piknik

Unnamed repository; edit this file 'description' to name the repository.
Info | Log | Files | Refs | README | LICENSE

commit 30a4a685c1d034cca0c905c174c5d6f7838dd842
parent b49ab2ceac92fe74c6063ee330c582a45599e226
Author: lash <dev@holbrook.no>
Date:   Fri,  2 Dec 2022 09:10:58 +0000

Introduce wrapper parent class to hold message content buffer

Diffstat:
Mpiknik/crypto.py | 10+++++++---
Mpiknik/msg.py | 8+++++---
Mpiknik/render/base.py | 75++++++++++++++++++++++++++++++++++++++++++++++++---------------------------
Mpiknik/render/ini.py | 31+++++++++++++++++--------------
Mpiknik/render/plain.py | 78++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mpiknik/runnable/show.py | 46+++++++++++++++++++++++++---------------------
Apiknik/wrap.py | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 197 insertions(+), 106 deletions(-)

diff --git a/piknik/crypto.py b/piknik/crypto.py @@ -10,14 +10,16 @@ import gnupg # local imports from piknik.error import VerifyError from piknik.msg import MessageEnvelope +from piknik.wrap import Wrapper -logg = logging.getLogger() +logg = logging.getLogger(__name__) logging.getLogger('gnupg').setLevel(logging.ERROR) -class PGPSigner: +class PGPSigner(Wrapper): def __init__(self, home_dir=None, default_key=None, passphrase=None, use_agent=False, skip_verify=False): + super(PGPSigner, self).__init__() self.gpg = gnupg.GPG(gnupghome=home_dir) self.default_key = default_key self.passphrase = passphrase @@ -69,16 +71,18 @@ class PGPSigner: return MessageEnvelope(msg) - def message_callback(self, envelope, msg, message_id): + def message_callback(self, envelope, msg, message_id, message_date): if msg.get('From') != None: envelope.sender = msg.get('From') if self.__envelope_state == 0: + self.add(message_id, msg) self.__envelope_state = 1 self.__envelope = msg return (envelope, msg,) if msg.get('Content-Type') != 'application/pgp-signature': + self.add(message_id, msg) return (envelope, msg,) v = self.__envelope.as_string() diff --git a/piknik/msg.py b/piknik/msg.py @@ -4,12 +4,11 @@ import uuid import mimetypes from base64 import b64encode import time - -#from email.message import EmailMessage as Message from email.message import Message from email import message_from_string from email.policy import Compat32 from email.utils import formatdate +from email.utils import parsedate_to_datetime logg = logging.getLogger(__name__) @@ -45,6 +44,7 @@ class IssueMessage: def __unwrap(self, msg, envelope_callback=rubber_stamp_envelope, message_callback=None, post_callback=None): message_ids = [] message_id = None + message_date = None envelope = None initial = False @@ -66,8 +66,10 @@ class IssueMessage: if new_message_id != None: message_id = new_message_id message_ids.append(message_id) + d = m.get('Date') + message_date = parsedate_to_datetime(d) else: - message_callback(envelope, m, message_id) + message_callback(envelope, m, message_id, message_date) if post_callback != None: post_callback(message_ids) diff --git a/piknik/render/base.py b/piknik/render/base.py @@ -1,21 +1,33 @@ -#import sys +# standard imports import logging +import sys + +# local imports +from piknik.msg import MessageEnvelope logg = logging.getLogger(__name__) +def stream_accumulator(v, w=sys.stdout): + w.write(v) + + class Renderer: - def __init__(self, basket, accumulator=None): + def __init__(self, basket, accumulator=None, envelope_callback=None, message_callback=None): self.b = basket #self.e = None self.a = accumulator + self.message_callback = message_callback + self.envelope_callback = envelope_callback - def __add(self, v): - if self.a != None: + def add(self, v, accumulator=None): + if accumulator == None: + accumulator = self.a + if accumulator != None: if v != None: - self.a(v) + accumulator(v) def apply_envelope_pre(self, state, issue, tags, envelope, accumulator=None): @@ -30,15 +42,15 @@ class Renderer: pass - def apply_message_pre(self, state, issue, tags, envelope, message, message_id, accumulator=None): + def apply_message_pre(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None): pass - def apply_message_post(self, state, issue, tags, envelope, message, message_id, accumulator=None): + def apply_message_post(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None): pass - def apply_message(self, state, issue, tags, envelope, message, message_id, accumulator=None): + def apply_message(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None): pass @@ -53,22 +65,31 @@ class Renderer: def apply_issue(self, state, issue, tags, accumulator=None): def envelope_callback(envelope, envelope_type): + if self.envelope_callback != None: + envelope = self.envelope_callback(envelope, envelope_type) + else: + envelope = MessageEnvelope(envelope) r = self.apply_envelope_pre(state, issue, tags, envelope, accumulator=accumulator) - self.__add(r) + self.add(r) r = self.apply_envelope(state, issue, tags, envelope, accumulator=accumulator) - self.__add(r) + self.add(r) r = self.apply_envelope_post(state, issue, tags, envelope, accumulator=accumulator) - self.__add(r) - - def message_callback(envelope, message, message_id): - r = self.apply_message_pre(state, issue, tags, envelope, message, message_id, accumulator=accumulator) - self.__add(r) - r = self.apply_message(state, issue, tags, envelope, message, message_id, accumulator=accumulator) - self.__add(r) - r = self.apply_message_post(state, issue, tags, envelope, message, message_id, accumulator=accumulator) - self.__add(r) + self.add(r) + return envelope + + def message_callback(envelope, message, message_id, message_date): + if self.message_callback != None: + (envelope, message) = self.message_callback(envelope, message, message_id, message_date) + r = self.apply_message_pre(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator) + self.add(r) + r = self.apply_message(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator) + self.add(r) + r = self.apply_message_post(state, issue, tags, envelope, message, message_id, message_date, accumulator=accumulator) + self.add(r) + return (envelope, message,) #for msg in self.b.get_msg(issue.id, envelope_callback=envelope_callback, message_callback=message_callback): + logg.debug('in msg') self.b.get_msg(issue.id, envelope_callback=envelope_callback, message_callback=message_callback) @@ -85,11 +106,11 @@ class Renderer: issue = self.b.get(issue_id) tags = self.b.tags(issue_id=issue_id) r = self.apply_issue_pre(state, issue, tags) - self.__add(r) + self.add(r) r = self.apply_issue(state, issue, tags) - self.__add(r) + self.add(r) r = self.apply_issue_post(state, issue, tags) - self.__add(r) + self.add(r) def apply_begin(self, accumulator=None): @@ -102,15 +123,15 @@ class Renderer: def apply(self, accumulator=None): r = self.apply_begin() - self.__add(r) + self.add(r) for state in self.b.states(): r = self.apply_state_pre(state) - self.__add(r) + self.add(r) r = self.apply_state(state) - self.__add(r) + self.add(r) r = self.apply_state_post(state) - self.__add(r) + self.add(r) r = self.apply_end() - self.__add(r) + self.add(r) diff --git a/piknik/render/ini.py b/piknik/render/ini.py @@ -1,25 +1,28 @@ -import sys - +# local imports from .base import Renderer as BaseRenderer +from .base import stream_accumulator class Renderer(BaseRenderer): - def apply_state(self, state, w=sys.stdout): - w.write('[' + state + ']\n') - - - def apply_issue(self, state, issue, tags, w=sys.stdout): - w.write('{}\t{}\t{}\n'.format(issue.title, ','.join(tags), issue.id)) + def __init__(self, basket, accumulator=stream_accumulator): + super(Renderer, self).__init__(basket, accumulator=accumulator) - def apply_state_post(self, state, w=sys.stdout): - w.write('\n') + def apply_state(self, state, accumulator=None): + s = '[' + state + ']\n' + self.add(s, accumulator=accumulator) + super(Renderer, self).apply_state(state, accumulator=accumulator) - def apply_message_part(self, state, issue, envelope, message, message_from, message_date, message_id, message_valid, dump_dir=None, w=sys.stdout): - pass + def apply_issue(self, state, issue, tags, accumulator=None): + s = '{}\t{}\t{}\n'.format( + issue.title, + ','.join(tags), + issue.id, + ) + self.add(s, accumulator=accumulator) - def apply_message_post(self, state, issue, tags, message, message_from, message_date, message_id, message_valid, w=sys.stdout): - pass + def apply_state_post(self, state, accumulator=None): + self.add('\n', accumulator=accumulator) diff --git a/piknik/render/plain.py b/piknik/render/plain.py @@ -1,22 +1,25 @@ # standard imports -import sys +import logging # external imports from mimeparse import parse_mime_type # local imports from .base import Renderer as BaseRenderer +from .base import stream_accumulator + +logg = logging.getLogger(__name__) class Renderer(BaseRenderer): - def __init__(self): - super(Renderer, self).__init__() - self.msg_buf = '' + def __init__(self, basket, dump_dir=None, accumulator=stream_accumulator, **kwargs): + super(Renderer, self).__init__(basket, accumulator=accumulator, **kwargs) + self.dump_dir = dump_dir - def apply_issue(self, state, issue, tags, w=sys.stdout): - w.write("""title: {} + def apply_issue(self, state, issue, tags, accumulator=None): + s = """title: {} tags: {} id: {} @@ -25,40 +28,38 @@ id: {} issue.title, ', '.join(tags), ) - ) + self.add(s, accumulator=accumulator) assigned = issue.get_assigned() if len(assigned) == 0: - w.write('(not assigned)\n') - return - - w.write('assigned to:\n') - owner = issue.owner() - for v in assigned: - o = v[0] - s = o.id() - if o == owner: - s += ' (owner)' - w.write('\t' + str(s)) - - - def apply_message_post(self, state, issue, tags, message, message_from, message_date, message_id, message_valid, w=sys.stdout): - r = self.msg_buf - self.msg_buf = '' - w.write('\nmessage {} from {} {} - {}\n\t{}\n'.format(message_date, message_from, message_valid, message_id, r)) - #return r - + self.add('(not assigned)\n', accumulator=accumulator) - #def apply_message(self, state, issue, tags, message, dump_dir=None, w=sys.stdout): - # return - - - #ww.seek(0) - #self.msg_buf += '\n\t' + ww.read() + '\n' - - - def apply_message_part(self, state, issue, envelope, message, message_from, message_date, message_id, message_valid, dump_dir=None, w=sys.stdout): + else: + self.add('assigned to:\n', accumulator=accumulator) + owner = issue.owner() + for v in assigned: + o = v[0] + s = o.id() + if o == owner: + s += ' (owner)' + s = '\t' + str(s) + '\n' + self.add(s, accumulator=accumulator) + + super(Renderer, self).apply_issue(state, issue, tags, accumulator=accumulator) + + + def apply_message(self, state, issue, tags, envelope, message, message_id, message_date, accumulator=None): + s = '\nmessage {} from {} {} - {}\n\n'.format( + message_date, + envelope.sender, + envelope.valid, + message_id, + ) + self.add(s, accumulator=accumulator) + + + def apply_message_part(self, state, issue, envelope, message, message_id, message_date, accumulator=None): m = parse_mime_type(message.get_content_type()) filename = message.get_filename() @@ -72,14 +73,15 @@ id: {} else: v = '[rich text]' else: - if dump_dir != None: + if self.dump_dir != None: v = message.get_payload() if message.get('Content-Transfer-Encoding') == 'BASE64': v = b64decode(v).decode() - filename = to_suffixed_file(dump_dir, filename, v) + filename = to_suffixed_file(self.dump_dir, filename, v) sz = message.get('Content-Length') if sz == None: sz = 'unknown' v = '[file: {}, type {}/{}, size: {}]'.format(filename, m[0], m[1], sz) - w.write('\n\t' + v + '\n') + s = '\n\t' + v + '\n' + self.add(s, accumulator=accumulator) diff --git a/piknik/runnable/show.py b/piknik/runnable/show.py @@ -50,7 +50,8 @@ def to_suffixed_file(d, s, data): # TODO can implement as email parser api instead? class PGPWrapper(PGPSigner): - def __init__(self, renderer, state, issue, home_dir=None): + #def __init__(self, renderer, state, issue, home_dir=None): + def __init__(self, state, issue, home_dir=None): #super(PGPWrapper, self).__init__(home_dir=home_dir) super(PGPWrapper, self).__init__(home_dir=home_dir, skip_verify=True) self.message_date = None @@ -59,18 +60,18 @@ class PGPWrapper(PGPSigner): self.message_id = None self.sender = None self.valid = False - self.renderer = renderer + #self.renderer = renderer self.state = state self.issue = issue - def render_message(self, envelope, messages, message_sender, message_date, message_id, dump_dir=None, w=sys.stdout): - for message in messages: - self.renderer.apply_message_part(self.state, self.issue, envelope, message, message_sender, message_date, message_id, self.valid, dump_dir=dump_dir, w=w) - #valid = '[++]' - #if not self.valid: - # valid = '[!!]' - self.renderer.apply_message_post(self.state, self.issue, envelope, message, self.sender, message_date, message_id, self.valid, w=w) +# def render_message(self, envelope, messages, message_sender, message_date, message_id, dump_dir=None, w=sys.stdout): +# for message in messages: +# self.renderer.apply_message_part(self.state, self.issue, envelope, message, message_sender, message_date, message_id, self.valid, dump_dir=dump_dir, w=w) +# #valid = '[++]' +# #if not self.valid: +# # valid = '[!!]' +# self.renderer.apply_message_post(self.state, self.issue, envelope, message, self.sender, message_date, message_id, self.valid, w=w) def envelope_callback(self, envelope, envelope_type): @@ -79,8 +80,8 @@ class PGPWrapper(PGPSigner): return envelope - def message_callback(self, envelope, message, message_id): - (envelope, message) = super(PGPWrapper, self).message_callback(envelope, message, message_id) + def message_callback(self, envelope, message, message_id, message_date): + (envelope, message) = super(PGPWrapper, self).message_callback(envelope, message, message_id, message_date) if envelope != None and not envelope.resolved: logg.debug('have sender {}'.format(self.sender)) @@ -89,7 +90,7 @@ class PGPWrapper(PGPSigner): self.resolved = True if message_id == None: - return + return (envelope, message,) messages = [] if message.get('X-Piknik-Msg-Id') == None: @@ -107,6 +108,8 @@ class PGPWrapper(PGPSigner): self.message_date = parsedate_to_datetime(d) self.message_id = message_id + return (envelope, message,) + def post_callback(self, messages_id): dump_dir = None @@ -194,19 +197,20 @@ def main(): #import piknik.render.html #renderer = piknik.render.html.Renderer() import piknik.render.ini - renderer = piknik.render.ini.Renderer() - return process_states(renderer, basket) - - import piknik.render.html - renderer = piknik.render.html.Renderer() - #import piknik.render.ini - #renderer = piknik.render.ini.Renderer() + renderer = piknik.render.ini.Renderer(basket) + #return process_states(renderer, basket) + renderer.apply() + return issue = basket.get(arg.issue_id) tags = basket.tags(arg.issue_id) + state = basket.get_state(arg.issue_id) + verifier = PGPWrapper(state, issue, home_dir=gpg_home) - #globals()['render_' + arg.renderer](basket, state, issue, tags) - render(renderer, basket, issue, tags) + import piknik.render.plain + renderer = piknik.render.plain.Renderer(basket, envelope_callback=verifier.envelope_callback, message_callback=verifier.message_callback) + + renderer.apply_issue(state, issue, tags) if __name__ == '__main__': diff --git a/piknik/wrap.py b/piknik/wrap.py @@ -0,0 +1,55 @@ +# standard imports +import logging + +# external imports +from mimeparse import parse_mime_type + +logg = logging.getLogger(__name__) + + +class Wrapper: + + def __init__(self): + self.content_buffer = {} + + + def add(self, message_id, message): + m = parse_mime_type(message.get_content_type()) + filename = message.get_filename() + + v = '' + if filename == None: + if m[0] == 'text': + if m[1] == 'plain': + v = message.get_payload() + if message.get('Content-Transfer-Encoding') == 'BASE64': + v = b64decode(v).decode() + else: + v = '[rich text]' + else: + if self.dump_dir != None: + v = message.get_payload() + if message.get('Content-Transfer-Encoding') == 'BASE64': + v = b64decode(v).decode() + filename = to_suffixed_file(self.dump_dir, filename, v) + + sz = message.get('Content-Length') + if sz == None: + sz = -1 + else: + sz = int(sz) + + self.content_buffer[message_id] = { + 'type': m, + 'filename': filename, + 'size': sz, + 'contents': v, + } + + logg.debug('buffered content {}'.format(self.content_buffer[message_id])) + + + def pop(self, message_id): + r = self.content_buffer[message_id] + del self.content_buffer[message_id] + return r