]> git.ipfire.org Git - pbs.git/blame - src/buildservice/messages.py
jobs: Use templates to sender job status emails
[pbs.git] / src / buildservice / messages.py
CommitLineData
9137135a
MT
1#!/usr/bin/python
2
68dd077d
MT
3import email
4import email.mime.multipart
5import email.mime.text
9137135a 6import logging
68dd077d 7import markdown
9137135a
MT
8import subprocess
9import tornado.locale
68dd077d 10import tornado.template
9137135a 11
2c909128 12from . import base
68dd077d
MT
13from . import users
14
15from .constants import TEMPLATESDIR
9137135a
MT
16
17class Messages(base.Object):
68dd077d
MT
18 def init(self):
19 self.templates = tornado.template.Loader(TEMPLATESDIR)
9137135a 20
68dd077d
MT
21 def __iter__(self):
22 messages = self.db.query("SELECT * FROM messages \
23 WHERE sent_at IS NULL ORDER BY queued_at")
9137135a 24
68dd077d 25 return iter(messages)
9137135a 26
68dd077d
MT
27 def __len__(self):
28 res = self.db.get("SELECT COUNT(*) AS count FROM messages \
29 WHERE sent_at IS NULL")
9137135a 30
68dd077d 31 return res.count
9137135a 32
68dd077d
MT
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
9137135a 111
68dd077d
MT
112 # Render the message
113 try:
114 part = t.generate(**namespace)
9137135a 115
68dd077d
MT
116 # Reset the rendered template when it could not be rendered
117 except:
118 self.templates.reset()
119 raise
9137135a 120
68dd077d
MT
121 # Parse the message
122 part = email.message_from_string(part)
c06ce6d1 123
68dd077d
MT
124 # Extract the headers
125 for k, v in part.items():
126 message.add_header(k, v)
c06ce6d1 127
68dd077d 128 body = part.get_payload()
81c849b0 129
68dd077d
MT
130 # Render markdown
131 if fmt == "markdown":
132 body = markdown.markdown(body)
9137135a 133
68dd077d
MT
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
1f43964c
MT
147 def send_template_to_many(self, recipients, *args, **kwargs):
148 for recipient in recipients:
149 self.send_template(recipient, *args, **kwargs)
150
68dd077d
MT
151 def queue(self, message):
152 res = self.db.get("INSERT INTO messages(message) VALUES(%s) RETURNING id", message)
153
154 logging.info("Message queued as %s", res.id)
155
156 def __sendmail(self, message):
157 # Convert message from string
158 msg = email.message_from_string(message.message)
159
160 # Get some headers
161 recipient = msg.get("To")
162 subject = msg.get("Subject")
163
164 logging.info("Sending mail to %s: %s" % (recipient, subject))
9137135a 165
68dd077d
MT
166 # Run sendmail and the email in
167 p = subprocess.Popen(["/usr/lib/sendmail", "-t"], bufsize=0, close_fds=True,
9137135a
MT
168 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
169
68dd077d 170 stdout, stderr = p.communicate(msg.as_string())
9137135a
MT
171
172 # Wait until sendmail has finished.
173 p.wait()
174
175 if p.returncode:
176 raise Exception, "Could not send mail: %s" % stderr
c06ce6d1 177
68dd077d
MT
178 # Mark message as sent
179 self.db.execute("UPDATE messages SET sent_at = NOW() WHERE id = %s", message.id)