|
Lines 17-24
This code requires a recent version of Andy Dustman's MySQLdb interface,
Link Here
|
| 17 |
Share and enjoy. |
17 |
Share and enjoy. |
| 18 |
""" |
18 |
""" |
| 19 |
|
19 |
|
| 20 |
import rfc822, mimetools, multifile, mimetypes, email.utils |
20 |
import email, mimetypes, email.utils |
| 21 |
import sys, re, glob, StringIO, os, stat, time |
21 |
import sys, re, glob, os, stat, time |
| 22 |
import MySQLdb, getopt |
22 |
import MySQLdb, getopt |
| 23 |
|
23 |
|
| 24 |
# mimetypes doesn't include everything we might encounter, yet. |
24 |
# mimetypes doesn't include everything we might encounter, yet. |
|
Lines 89-98
def process_notes_file(current, fname):
Link Here
|
| 89 |
def process_reply_file(current, fname): |
89 |
def process_reply_file(current, fname): |
| 90 |
new_note = {} |
90 |
new_note = {} |
| 91 |
reply = open(fname, "r") |
91 |
reply = open(fname, "r") |
| 92 |
msg = rfc822.Message(reply) |
92 |
msg = email.message_from_file(reply) |
| 93 |
new_note['text'] = "%s\n%s" % (msg['From'], msg.fp.read()) |
93 |
|
| 94 |
new_note['timestamp'] = email.utils.parsedate_tz(msg['Date']) |
94 |
# Add any attachments that may have been in a followup or reply |
| 95 |
current["notes"].append(new_note) |
95 |
msgtype = msg.get_content_maintype() |
|
|
96 |
if msgtype == "multipart": |
| 97 |
for part in msg.walk(): |
| 98 |
new_note = {} |
| 99 |
if part.get_filename() is None: |
| 100 |
if part.get_content_type() == "text/plain": |
| 101 |
new_note['timestamp'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date']))) |
| 102 |
new_note['text'] = "%s\n%s" % (msg['From'], part.get_payload()) |
| 103 |
current["notes"].append(new_note) |
| 104 |
else: |
| 105 |
maybe_add_attachment(part, current) |
| 106 |
else: |
| 107 |
new_note['text'] = "%s\n%s" % (msg['From'], msg.get_payload()) |
| 108 |
new_note['timestamp'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date']))) |
| 109 |
current["notes"].append(new_note) |
| 96 |
|
110 |
|
| 97 |
def add_notes(current): |
111 |
def add_notes(current): |
| 98 |
"""Add any notes that have been recorded for the current bug.""" |
112 |
"""Add any notes that have been recorded for the current bug.""" |
|
Lines 104-154
def add_notes(current):
Link Here
|
| 104 |
for f in glob.glob("%d.followup.*" % current['number']): |
118 |
for f in glob.glob("%d.followup.*" % current['number']): |
| 105 |
process_reply_file(current, f) |
119 |
process_reply_file(current, f) |
| 106 |
|
120 |
|
| 107 |
def maybe_add_attachment(current, file, submsg): |
121 |
def maybe_add_attachment(submsg, current): |
| 108 |
"""Adds the attachment to the current record""" |
122 |
"""Adds the attachment to the current record""" |
| 109 |
cd = submsg["Content-Disposition"] |
123 |
attachment_filename = submsg.get_filename() |
| 110 |
m = re.search(r'filename="([^"]+)"', cd) |
124 |
if attachment_filename is None: |
| 111 |
if m == None: |
|
|
| 112 |
return |
125 |
return |
| 113 |
attachment_filename = m.group(1) |
126 |
|
| 114 |
if (submsg.gettype() == 'application/octet-stream'): |
127 |
if (submsg.get_content_type() == 'application/octet-stream'): |
| 115 |
# try get a more specific content-type for this attachment |
128 |
# try get a more specific content-type for this attachment |
| 116 |
type, encoding = mimetypes.guess_type(m.group(1)) |
129 |
mtype, encoding = mimetypes.guess_type(attachment_filename) |
| 117 |
if type == None: |
130 |
if mtype == None: |
| 118 |
type = submsg.gettype() |
131 |
mtype = submsg.get_content_type() |
| 119 |
else: |
132 |
else: |
| 120 |
type = submsg.gettype() |
133 |
mtype = submsg.get_content_type() |
| 121 |
|
134 |
|
| 122 |
try: |
135 |
if mtype == 'application/x-pkcs7-signature': |
| 123 |
data = StringIO.StringIO() |
136 |
return |
| 124 |
mimetools.decode(file, data, submsg.getencoding()) |
137 |
|
| 125 |
except: |
138 |
if mtype == 'application/pkcs7-signature': |
|
|
139 |
return |
| 140 |
|
| 141 |
if mtype == 'application/pgp-signature': |
| 126 |
return |
142 |
return |
| 127 |
|
143 |
|
| 128 |
current['attachments'].append( ( attachment_filename, type, data.getvalue() ) ) |
144 |
if mtype == 'message/rfc822': |
|
|
145 |
return |
| 129 |
|
146 |
|
| 130 |
def process_mime_body(current, file, submsg): |
|
|
| 131 |
data = StringIO.StringIO() |
| 132 |
try: |
147 |
try: |
| 133 |
mimetools.decode(file, data, submsg.getencoding()) |
148 |
data = submsg.get_payload(decode=True) |
| 134 |
current['description'] = data.getvalue() |
|
|
| 135 |
except: |
149 |
except: |
| 136 |
return |
150 |
return |
| 137 |
|
151 |
|
|
|
152 |
current['attachments'].append( ( attachment_filename, mtype, data ) ) |
| 153 |
|
| 138 |
def process_text_plain(msg, current): |
154 |
def process_text_plain(msg, current): |
| 139 |
current['description'] = msg.fp.read() |
155 |
current['description'] = msg.get_payload() |
| 140 |
|
156 |
|
| 141 |
def process_multi_part(file, msg, current): |
157 |
def process_multi_part(msg, current): |
| 142 |
mf = multifile.MultiFile(file) |
158 |
for part in msg.walk(): |
| 143 |
mf.push(msg.getparam("boundary")) |
159 |
if part.get_filename() is None: |
| 144 |
while mf.next(): |
160 |
process_text_plain(part, current) |
| 145 |
submsg = mimetools.Message(file) |
|
|
| 146 |
if submsg.has_key("Content-Disposition"): |
| 147 |
maybe_add_attachment(current, mf, submsg) |
| 148 |
else: |
161 |
else: |
| 149 |
# This is the message body itself (always?), so process |
162 |
maybe_add_attachment(part, current) |
| 150 |
# accordingly |
|
|
| 151 |
process_mime_body(current, mf, submsg) |
| 152 |
|
163 |
|
| 153 |
def process_jitterbug(filename): |
164 |
def process_jitterbug(filename): |
| 154 |
current = {} |
165 |
current = {} |
|
Lines 158-196
def process_jitterbug(filename):
Link Here
|
| 158 |
current['description'] = '' |
169 |
current['description'] = '' |
| 159 |
current['date-reported'] = () |
170 |
current['date-reported'] = () |
| 160 |
current['short-description'] = '' |
171 |
current['short-description'] = '' |
| 161 |
|
|
|
| 162 |
print "Processing: %d" % current['number'] |
| 163 |
|
172 |
|
| 164 |
file = open(filename, "r") |
173 |
print "Processing: %d" % current['number'] |
| 165 |
create_date = os.fstat(file.fileno()) |
|
|
| 166 |
msg = mimetools.Message(file) |
| 167 |
|
174 |
|
| 168 |
msgtype = msg.gettype() |
175 |
mfile = open(filename, "r") |
|
|
176 |
create_date = os.fstat(mfile.fileno()) |
| 177 |
msg = email.message_from_file(mfile) |
| 169 |
|
178 |
|
| 170 |
add_notes(current) |
179 |
current['date-reported'] = time.gmtime(email.utils.mktime_tz(email.utils.parsedate_tz(msg['Date']))) |
| 171 |
current['date-reported'] = email.utils.parsedate_tz(msg['Date']) |
|
|
| 172 |
if current['date-reported'] is None: |
180 |
if current['date-reported'] is None: |
| 173 |
current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME]) |
181 |
current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME]) |
| 174 |
|
182 |
|
| 175 |
if current['date-reported'][0] < 1900: |
183 |
if current['date-reported'][0] < 1900: |
| 176 |
current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME]) |
184 |
current['date-reported'] = time.gmtime(create_date[stat.ST_MTIME]) |
| 177 |
|
185 |
|
| 178 |
if msg.getparam('Subject') is not None: |
186 |
if msg.has_key('Subject') is not False: |
| 179 |
current['short-description'] = msg['Subject'] |
187 |
current['short-description'] = msg['Subject'] |
| 180 |
else: |
188 |
else: |
| 181 |
current['short-description'] = "Unknown" |
189 |
current['short-description'] = "Unknown" |
| 182 |
|
190 |
|
| 183 |
if msgtype[:5] == 'text/': |
191 |
msgtype = msg.get_content_maintype() |
|
|
192 |
if msgtype == 'text': |
| 184 |
process_text_plain(msg, current) |
193 |
process_text_plain(msg, current) |
| 185 |
elif msgtype[:5] == 'text': |
194 |
elif msgtype == "multipart": |
| 186 |
process_text_plain(msg, current) |
195 |
process_multi_part(msg, current) |
| 187 |
elif msgtype[:10] == "multipart/": |
|
|
| 188 |
process_multi_part(file, msg, current) |
| 189 |
else: |
196 |
else: |
| 190 |
# Huh? This should never happen. |
197 |
# Huh? This should never happen. |
| 191 |
print "Unknown content-type: %s" % msgtype |
198 |
print "Unknown content-type: %s" % msgtype |
| 192 |
sys.exit(1) |
199 |
sys.exit(1) |
| 193 |
|
200 |
|
|
|
201 |
add_notes(current) |
| 202 |
|
| 194 |
# At this point we have processed the message: we have all of the notes and |
203 |
# At this point we have processed the message: we have all of the notes and |
| 195 |
# attachments stored, so it's time to add things to the database. |
204 |
# attachments stored, so it's time to add things to the database. |
| 196 |
# The schema for JitterBug 2.14 can be found at: |
205 |
# The schema for JitterBug 2.14 can be found at: |
|
Lines 219-224
def process_jitterbug(filename):
Link Here
|
| 219 |
try: |
228 |
try: |
| 220 |
cursor.execute( "INSERT INTO bugs SET " \ |
229 |
cursor.execute( "INSERT INTO bugs SET " \ |
| 221 |
"bug_id=%s," \ |
230 |
"bug_id=%s," \ |
|
|
231 |
"priority='---'," \ |
| 222 |
"bug_severity='normal'," \ |
232 |
"bug_severity='normal'," \ |
| 223 |
"bug_status=%s," \ |
233 |
"bug_status=%s," \ |
| 224 |
"creation_ts=%s," \ |
234 |
"creation_ts=%s," \ |
|
Lines 242-248
def process_jitterbug(filename):
Link Here
|
| 242 |
version, |
252 |
version, |
| 243 |
component, |
253 |
component, |
| 244 |
resolution] ) |
254 |
resolution] ) |
| 245 |
|
255 |
|
| 246 |
# This is the initial long description associated with the bug report |
256 |
# This is the initial long description associated with the bug report |
| 247 |
cursor.execute( "INSERT INTO longdescs SET " \ |
257 |
cursor.execute( "INSERT INTO longdescs SET " \ |
| 248 |
"bug_id=%s," \ |
258 |
"bug_id=%s," \ |
|
Lines 253-259
def process_jitterbug(filename):
Link Here
|
| 253 |
reporter, |
263 |
reporter, |
| 254 |
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]), |
264 |
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]), |
| 255 |
current['description'] ] ) |
265 |
current['description'] ] ) |
| 256 |
|
266 |
|
| 257 |
# Add whatever notes are associated with this defect |
267 |
# Add whatever notes are associated with this defect |
| 258 |
for n in current['notes']: |
268 |
for n in current['notes']: |
| 259 |
cursor.execute( "INSERT INTO longdescs SET " \ |
269 |
cursor.execute( "INSERT INTO longdescs SET " \ |
|
Lines 265-279
def process_jitterbug(filename):
Link Here
|
| 265 |
reporter, |
275 |
reporter, |
| 266 |
time.strftime("%Y-%m-%d %H:%M:%S", n['timestamp'][:9]), |
276 |
time.strftime("%Y-%m-%d %H:%M:%S", n['timestamp'][:9]), |
| 267 |
n['text']]) |
277 |
n['text']]) |
| 268 |
|
278 |
|
| 269 |
# add attachments associated with this defect |
279 |
# add attachments associated with this defect |
| 270 |
for a in current['attachments']: |
280 |
for a in current['attachments']: |
| 271 |
cursor.execute( "INSERT INTO attachments SET " \ |
281 |
cursor.execute( "INSERT INTO attachments SET " \ |
| 272 |
"bug_id=%s, creation_ts=%s, description='', mimetype=%s," \ |
282 |
"bug_id=%s, creation_ts=%s, description=%s, mimetype=%s," \ |
| 273 |
"filename=%s, submitter_id=%s", |
283 |
"filename=%s, submitter_id=%s", |
| 274 |
[ current['number'], |
284 |
[ current['number'], |
| 275 |
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]), |
285 |
time.strftime("%Y-%m-%d %H:%M:%S", current['date-reported'][:9]), |
| 276 |
a[1], a[0], reporter ]) |
286 |
a[0], a[1], a[0], reporter ]) |
| 277 |
cursor.execute( "INSERT INTO attach_data SET " \ |
287 |
cursor.execute( "INSERT INTO attach_data SET " \ |
| 278 |
"id=LAST_INSERT_ID(), thedata=%s", |
288 |
"id=LAST_INSERT_ID(), thedata=%s", |
| 279 |
[ a[2] ]) |
289 |
[ a[2] ]) |