]>
Commit | Line | Data |
---|---|---|
9137135a MT |
1 | #!/usr/bin/python |
2 | ||
68dd077d MT |
3 | import email |
4 | import email.mime.multipart | |
5 | import email.mime.text | |
9137135a | 6 | import logging |
68dd077d | 7 | import markdown |
9137135a MT |
8 | import subprocess |
9 | import tornado.locale | |
68dd077d | 10 | import tornado.template |
9137135a | 11 | |
2c909128 | 12 | from . import base |
68dd077d MT |
13 | from . import users |
14 | ||
15 | from .constants import TEMPLATESDIR | |
9137135a MT |
16 | |
17 | class 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) |