]> git.ipfire.org Git - pbs.git/blob - src/buildservice/messages.py
messages: Refactor module
[pbs.git] / src / buildservice / messages.py
1 #!/usr/bin/python
2
3 import email
4 import email.mime.multipart
5 import email.mime.text
6 import logging
7 import markdown
8 import subprocess
9 import tornado.locale
10 import tornado.template
11
12 from . import base
13 from . import users
14
15 from .constants import TEMPLATESDIR
16
17 class Messages(base.Object):
18 def init(self):
19 self.templates = tornado.template.Loader(TEMPLATESDIR)
20
21 def __iter__(self):
22 messages = self.db.query("SELECT * FROM messages \
23 WHERE sent_at IS NULL ORDER BY queued_at")
24
25 return iter(messages)
26
27 def __len__(self):
28 res = self.db.get("SELECT COUNT(*) AS count FROM messages \
29 WHERE sent_at IS NULL")
30
31 return res.count
32
33 def process_queue(self):
34 """
35 Sends all emails in the queue
36 """
37 for message in self:
38 with self.db.transaction():
39 self.__sendmail(message)
40
41 # Delete all old emails
42 with self.db.transaction():
43 self.cleanup()
44
45 def cleanup(self):
46 self.db.execute("DELETE FROM messages WHERE sent_at <= NOW() - INTERVAL '24 hours'")
47
48 def send_to(self, recipient, message, sender=None, headers={}):
49 # Parse the message
50 if not isinstance(message, email.message.Message):
51 message = email.message_from_string(message)
52
53 if not sender:
54 sender = self.backend.settings.get("email_from", "Pakfire Build Service <no-reply@ipfire.org>")
55
56 # Add sender
57 message.add_header("From", sender)
58
59 # Add recipient
60 message.add_header("To", recipient)
61
62 # Sending this message now
63 message.add_header("Date", email.utils.formatdate())
64
65 # Add sender program
66 message.add_header("X-Mailer", "Pakfire Build Service %s" % self.backend.version)
67
68 # Add any headers
69 for k, v in headers.items():
70 message.add_header(k, v)
71
72 # Queue the message
73 self.queue(message.as_string())
74
75 def send_template(self, recipient, name, sender=None, headers={}, **kwargs):
76 # Get user (if we have one)
77 if isinstance(recipient, users.User):
78 user = recipient
79 else:
80 user = self.backend.users.find_maintainer(recipient)
81
82 # Get the user's locale or use default
83 if user:
84 locale = user.locale
85 else:
86 locale = tornado.locale.get()
87
88 # Create namespace
89 namespace = {
90 "baseurl" : self.settings.get("baseurl"),
91 "recipient" : recipient,
92 "user" : user,
93
94 # Locale
95 "locale" : locale,
96 "_" : locale.translate,
97 }
98 namespace.update(kwargs)
99
100 # Create a MIMEMultipart message.
101 message = email.mime.multipart.MIMEMultipart()
102
103 # Create an alternating multipart message to show HTML or text
104 alternative = email.mime.multipart.MIMEMultipart("alternative")
105
106 for fmt, mimetype in (("txt", "plain"), ("html", "html"), ("markdown", "html")):
107 try:
108 t = self.templates.load("%s.%s" % (name, fmt))
109 except IOError:
110 continue
111
112 # Render the message
113 try:
114 part = t.generate(**namespace)
115
116 # Reset the rendered template when it could not be rendered
117 except:
118 self.templates.reset()
119 raise
120
121 # Parse the message
122 part = email.message_from_string(part)
123
124 # Extract the headers
125 for k, v in part.items():
126 message.add_header(k, v)
127
128 body = part.get_payload()
129
130 # Render markdown
131 if fmt == "markdown":
132 body = markdown.markdown(body)
133
134 # Compile part again
135 part = email.mime.text.MIMEText(body, mimetype, "utf-8")
136
137 # Attach the parts to the mime container
138 # According to RFC2046, the last part of a multipart message is preferred
139 alternative.attach(part)
140
141 # Add alternative section to outer message
142 message.attach(alternative)
143
144 # Send the message
145 self.send_to(user.email.recipient if user else recipient, message, sender=sender, headers=headers)
146
147 def queue(self, message):
148 res = self.db.get("INSERT INTO messages(message) VALUES(%s) RETURNING id", message)
149
150 logging.info("Message queued as %s", res.id)
151
152 def __sendmail(self, message):
153 # Convert message from string
154 msg = email.message_from_string(message.message)
155
156 # Get some headers
157 recipient = msg.get("To")
158 subject = msg.get("Subject")
159
160 logging.info("Sending mail to %s: %s" % (recipient, subject))
161
162 # Run sendmail and the email in
163 p = subprocess.Popen(["/usr/lib/sendmail", "-t"], bufsize=0, close_fds=True,
164 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
165
166 stdout, stderr = p.communicate(msg.as_string())
167
168 # Wait until sendmail has finished.
169 p.wait()
170
171 if p.returncode:
172 raise Exception, "Could not send mail: %s" % stderr
173
174 # Mark message as sent
175 self.db.execute("UPDATE messages SET sent_at = NOW() WHERE id = %s", message.id)