• R/O
  • SSH
  • HTTPS

shibuya-trac: Commit


Commit MetaInfo

Revision805 (tree)
Zeit2011-08-05 08:08:31
Autorjun66j5

Log Message

Port to Trac 0.12.x. Added mailarchive subcommands of trac-admin instread of TracMailArchive-admin command.

Ändern Zusammenfassung

Diff

--- plugins/mailarchiveplugin/branches/0.12/mailarchive/mailarchiveadmin.py (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/mailarchiveadmin.py (revision 805)
@@ -0,0 +1,518 @@
1+# -*- coding: utf-8 -*-
2+# MailArchive plugin
3+
4+import calendar
5+import email
6+import email.Errors
7+import email.Utils
8+import mailbox
9+import mimetypes
10+import os
11+import poplib
12+import re
13+import time
14+import traceback
15+from email.Utils import unquote
16+
17+from trac.core import Component, implements
18+from trac.admin.api import IAdminCommandProvider
19+from trac.util import NaivePopen
20+from trac.attachment import Attachment
21+
22+
23+class MailArchiveAdmin(Component):
24+ implements(IAdminCommandProvider)
25+
26+ def get_admin_commands(self):
27+ yield ('mailarchive import', '<mlname> <filepath>',
28+ 'import UnixMail',
29+ None, self._do_import)
30+ yield ('mailarchive pop3', '<mlname>',
31+ 'import from pop3 server',
32+ None, self._do_pop3)
33+
34+
35+ def all_docs(cls):
36+ return (cls._help_help)
37+ all_docs = classmethod(all_docs)
38+
39+
40+
41+ def msgfactory(self,fp):
42+ try:
43+ return email.message_from_file(fp)
44+ except email.Errors.MessageParseError:
45+ # Don't return None since that will
46+ # stop the mailbox iterator
47+ return ''
48+
49+ def decode_to_unicode(self, basestr):
50+ # http://www.python.jp/pipermail/python-ml-jp/2004-June/002932.html
51+ # Make mail header string to unicode string
52+
53+ decodefrag = email.Header.decode_header(basestr)
54+ subj_fragments = ['',]
55+ for frag, enc in decodefrag:
56+ if enc:
57+ frag = self.to_unicode(frag, enc)
58+ subj_fragments.append(frag)
59+ return ''.join(subj_fragments)
60+
61+ def to_unicode(self,text,charset):
62+ if text=='':
63+ return ''
64+
65+ default_charset = self.env.config.get('mailarchive', 'default_charset',None)
66+ if default_charset :
67+ charset = default_charset
68+
69+ # to unicode with codecaliases
70+ # codecaliases change mail charset to python charset
71+ charset = charset.lower( )
72+ aliases = {}
73+ aliases_text = self.env.config.get('mailarchive', 'codecaliases')
74+ for alias in aliases_text.split(','):
75+ alias_s = alias.split(':')
76+ if len(alias_s) >=2:
77+ if alias_s[1] == 'cmd':
78+ aliases[alias_s[0].lower()] = ('cmd',alias_s[2])
79+ else:
80+ aliases[alias_s[0].lower()] = ('codec',alias_s[1])
81+
82+ if aliases.has_key(charset):
83+ (type,alias) = aliases[charset]
84+ if type == 'codec':
85+ text = unicode(text,alias)
86+ elif type == 'cmd':
87+ np = NaivePopen(alias, text, capturestderr=1)
88+ if np.errorlevel or np.err:
89+ err = 'Failed: %s, %s.' % (np.errorlevel, np.err)
90+ raise Exception, err
91+ text = unicode(np.out,'utf-8')
92+ else:
93+ text = unicode(text,charset)
94+ return text
95+
96+ def import_message(self, msg, author,mlid, db):
97+ OUTPUT_ENCODING = 'utf-8'
98+ subject = ''
99+ messageid = ''
100+ utcdate = 0
101+ localdate = 0
102+ zoneoffset = 0
103+ text = ''
104+ body = ''
105+ ref_messageid = ''
106+
107+ cursor = db.cursor()
108+ is_newid = False
109+
110+ if 'message-id' in msg:
111+ messageid = msg['message-id']
112+ if messageid[:1] == '<':
113+ messageid = messageid[1:]
114+ if messageid[-1:] == '>':
115+ messageid = messageid[:-1]
116+ self.print_debug('Message-ID:%s' % messageid )
117+
118+ #check messageid is unique
119+ self.print_debug("Creating new mailarc '%s'" % 'mailarc')
120+ cursor.execute("SELECT id from mailarc WHERE messageid=%s",(messageid,))
121+ row = cursor.fetchone()
122+ id = None
123+ if row:
124+ id = row[0]
125+ if id == None or id == "":
126+ # why? get_last_id return 0 at first.
127+ #id = db.get_last_id(cursor, 'mailarc')
128+ is_newid = True
129+ cursor.execute("SELECT Max(id)+1 as id from mailarc")
130+ row = cursor.fetchone()
131+ if row and row[0] != None:
132+ id = row[0]
133+ else:
134+ id = 1
135+ id = int(id) # Because id might be 'n.0', int() is called.
136+
137+
138+ if 'date' in msg:
139+ datetuple_tz = email.Utils.parsedate_tz(msg['date'])
140+ localdate = calendar.timegm(datetuple_tz[:9]) #toDB
141+ zoneoffset = datetuple_tz[9] # toDB
142+ utcdate = localdate-zoneoffset # toDB
143+ #make zone ( +HHMM or -HHMM
144+ zone = ''
145+ if zoneoffset >0:
146+ zone = '+' + time.strftime('%H%M',time.gmtime(zoneoffset))
147+ elif zoneoffset < 0:
148+ zone = '-' + time.strftime('%H%M',time.gmtime(-1*zoneoffset))
149+
150+ #self.print_debug( time.strftime("%y/%m/%d %H:%M:%S %z",datetuple_tz[:9]))
151+ self.print_debug( time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(utcdate)))
152+ self.print_debug( time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(localdate)))
153+ self.print_debug(zone)
154+
155+ fromname,fromaddr = email.Utils.parseaddr(msg['from'])
156+ fromname = self.decode_to_unicode(fromname)
157+ fromaddr = self.decode_to_unicode(fromaddr)
158+
159+ self.print_info( ' ' + time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(localdate))+' ' + zone +' '+ fromaddr)
160+
161+ if 'subject' in msg:
162+ subject = self.decode_to_unicode(msg['subject'])
163+ self.print_debug( subject.encode(OUTPUT_ENCODING))
164+
165+ # make thread infomations
166+ ref_messageid = ''
167+ if 'in-reply-to' in msg:
168+ ref_messageid = ref_messageid + msg['In-Reply-To'] + ' '
169+ self.print_debug('In-Reply-To:%s' % ref_messageid )
170+
171+ if 'references' in msg:
172+ ref_messageid = ref_messageid + msg['References'] + ' '
173+
174+ m = re.findall(r'<(.+?)>', ref_messageid)
175+ ref_messageid = ''
176+ for text in m:
177+ ref_messageid = ref_messageid + "'%s'," % text
178+ ref_messageid = ref_messageid.strip(',')
179+ self.print_debug('RefMessage-ID:%s' % ref_messageid )
180+
181+
182+ # multipart mail
183+ if msg.is_multipart():
184+ body = ''
185+ # delete all attachement at message-id
186+ Attachment.delete_all(self.env, 'mailarchive', id, db)
187+
188+ for part in msg.walk():
189+ content_type = part.get_content_type()
190+ self.print_debug('Content-Type:'+content_type)
191+ file_counter = 1
192+
193+ if content_type == 'multipart/mixed':
194+ pass
195+ elif content_type == 'text/html' and self.is_file(part) == False:
196+ body = part.get_payload(decode=1)
197+ elif content_type == 'text/plain' and self.is_file(part) == False:
198+ body = part.get_payload(decode=1)
199+ charset = part.get_content_charset()
200+ self.print_debug('charset:'+str(charset))
201+ # Todo:need try
202+ if charset != None:
203+ body = self.to_unicode(body,charset)
204+ elif part.get_payload(decode=1) == None:
205+ pass
206+ else:
207+ self.print_debug( part.get_content_type())
208+ # get filename
209+ # Applications should really sanitize the given filename so that an
210+ # email message can't be used to overwrite important files
211+ filename = self.get_filename(part)
212+ if not filename:
213+ ext = mimetypes.guess_extension(part.get_content_type())
214+ if not ext:
215+ # Use a generic bag-of-bits extension
216+ ext = '.bin'
217+ filename = 'part-%03d%s' % (file_counter, ext)
218+ file_counter += 1
219+
220+ self.print_debug("filename:" + filename.encode(OUTPUT_ENCODING))
221+
222+ # make attachment
223+ tmp = os.tmpfile()
224+ tempsize =len(part.get_payload(decode=1))
225+ tmp.write(part.get_payload(decode=1))
226+
227+ tmp.flush()
228+ tmp.seek(0,0)
229+
230+ attachment = Attachment(self.env,'mailarchive', id)
231+
232+ attachment.description = '' # req.args.get('description', '')
233+ attachment.author = author #req.args.get('author', '')
234+ attachment.ipnr = '127.0.0.1'
235+
236+ try:
237+ attachment.insert(filename,
238+ tmp, tempsize,None,db)
239+ except Exception, e:
240+ try:
241+ ext = filename.split('.')[-1]
242+ if ext == filename:
243+ ext = '.bin'
244+ else:
245+ ext = '.' + ext
246+ filename = 'part-%03d%s' % (file_counter, ext)
247+ file_counter += 1
248+ attachment.insert(filename,
249+ tmp, tempsize,None,db)
250+ self.print_warning('As name is too long, the attached file is renamed : '+filename)
251+
252+ except Exception, e:
253+ self.print_error('Exception at attach file of Message-ID:'+messageid)
254+ self.print_error( e )
255+
256+ tmp.close()
257+
258+ # not multipart mail
259+ else:
260+ # Todo:if Content-Type = text/html then convert htmlMail to text
261+ content_type = msg.get_content_type()
262+ self.print_debug('Content-Type:'+content_type)
263+ if content_type == 'text/html':
264+ body = 'html'
265+ else:
266+ #body
267+ #self.print_debug(msg.get_content_type())
268+ body = msg.get_payload(decode=1)
269+ charset = msg.get_content_charset()
270+
271+ # need try:
272+ if charset != None:
273+ self.print_debug("charset:"+charset)
274+ body = self.to_unicode(body,charset)
275+
276+
277+ #body = body.replace(os.linesep,'\n')
278+ self.print_debug('Thread')
279+
280+ thread_parent = ref_messageid.replace("'",'').replace(',',' ')
281+ thread_root = ''
282+ if thread_parent !='':
283+ # sarch first parent id
284+ self.print_debug("SearchThread;"+thread_parent)
285+ cursor = db.cursor()
286+ sql = "SELECT threadroot,messageid FROM mailarc where messageid in (%s)" % ref_messageid
287+ self.print_debug(sql)
288+ cursor.execute(sql)
289+
290+ row = cursor.fetchone()
291+ if row:
292+ #thread_parent = row[1]
293+ if row[0] == '':
294+ thread_root = thread_parent.split(' ').pop()
295+ self.print_debug("AddToThread;"+thread_root)
296+ else:
297+ thread_root = row[0]
298+ self.print_debug("NewThread;"+thread_root)
299+ else:
300+ self.print_debug("NoThread;"+thread_parent)
301+ thread_root = thread_root.strip()
302+
303+ self.print_debug('Insert')
304+
305+ if messageid != '':
306+
307+ # insert or update mailarc_category
308+
309+ yearmonth = time.strftime("%Y%m",time.gmtime(utcdate))
310+ category = mlid+yearmonth
311+ cursor.execute("SELECT category,mlid,yearmonth,count FROM mailarc_category WHERE category=%s",
312+ (category,))
313+ row = cursor.fetchone()
314+ count = 0
315+ if row:
316+ count = row[3]
317+ pass
318+ else:
319+ cursor.execute("INSERT INTO mailarc_category (category,mlid,yearmonth,count) VALUES(%s,%s,%s,%s)",
320+ (category, mlid, yearmonth, 0))
321+ if is_newid == True:
322+ count = count +1
323+ cursor.execute("UPDATE mailarc_category SET count=%s WHERE category=%s" ,
324+ (count, category))
325+
326+ # insert or update mailarc
327+
328+ #self.print_debug(
329+ # "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" %(str(id),
330+ # category.encode('utf-8'),
331+ # messageid,
332+ # utcdate,
333+ # zoneoffset,
334+ # subject.encode('utf-8'), fromname.encode('utf-8'),
335+ # fromaddr.encode('utf-8'),'','',
336+ # thread_root,thread_parent))
337+ cursor.execute("DELETE FROM mailarc where messageid=%s",(messageid,))
338+ cursor.execute("INSERT INTO mailarc ("
339+ "id,category,messageid,utcdate,zoneoffset,subject,"
340+ "fromname,fromaddr,header,text, threadroot,threadparent ) "
341+ "VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)",
342+ (id, category, messageid, utcdate, zoneoffset, subject,
343+ fromname, fromaddr,'',body, thread_root,thread_parent))
344+
345+ db.commit()
346+
347+ def do_refresh_category(self,line):
348+ db = self.db_open()
349+ self.env = self.env_open()
350+ cursor = db.cursor()
351+ cursor.execute("DELETE FROM mailarc_category")
352+ cursor.execute("SELECT category, count(*) as cnt from mailarc GROUP BY category ")
353+ for category,cnt in cursor:
354+ cursor2 = db.cursor()
355+ cursor2.execute("INSERT INTO mailarc_category (category,mlid,yearmonth,count) VALUES(%s,%s,%s,%s)",(category,category[:-6],category[-6:],cnt))
356+ db.commit()
357+
358+ def _do_import(self, mlname, filepath):
359+ @self.env.with_transaction()
360+ def do_import(db):
361+ self._import_unixmailbox('cmd', db, mlname, filepath)
362+
363+ def _do_pop3(self, mlname):
364+ @self.env.with_transaction()
365+ def do_pop3(db):
366+ self._import_from_pop3('cmd', db, mlname)
367+
368+ def print_info(self,line):
369+ print "%s" % line
370+
371+ def print_debug(self,line):
372+ #print "[Debug] %s" % line
373+ pass
374+
375+ def print_error(self,line):
376+ print "[Error] %s" % line
377+
378+ def print_warning(self,line):
379+ print "[Warning] %s" % line
380+
381+ def _import_unixmailbox(self,author, db, mlid, msgfile_path):
382+ self.print_debug('import_mail')
383+
384+ #paser = Parser()
385+
386+ self.print_info("%s Start Importing %s ..." %
387+ (time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime()),msgfile_path))
388+
389+ fp = None
390+ try:
391+ fp = open(msgfile_path,"rb")
392+ mbox = mailbox.UnixMailbox(fp, self.msgfactory)
393+
394+ counter =1
395+ msg = mbox.next()
396+ while msg is not None:
397+ messageid = ''
398+ try:
399+ messageid = msg['message-id']
400+ self.import_message(msg,author,mlid,db)
401+ except Exception, e:
402+ self.print_error('Exception At Message-ID: %r' % (messageid,))
403+ self.print_error( e )
404+ traceback.print_exc()
405+
406+ if counter > 10000:
407+ break
408+ msg = mbox.next()
409+ counter = counter + 1
410+ finally:
411+ fp.close()
412+
413+ self.print_info("End Imporing %s. " % msgfile_path)
414+
415+ def _import_from_pop3(self,author, db, mlid):
416+
417+ pop_server = self.env.config.get('mailarchive', 'pop3_server')
418+ pop_user = self.env.config.get('mailarchive', 'pop3_user')
419+ pop_password = self.env.config.get('mailarchive', 'pop3_password')
420+ pop_delete = self.env.config.get('mailarchive', 'pop3_delete','none')
421+
422+ if pop_server =='':
423+ self.print_error('trac.ini mailarchive pop3_server is null!')
424+ elif pop_user == '':
425+ self.print_error('trac.ini mailarchive pop3_user is null!')
426+ elif pop_password == '':
427+ self.print_error('trac.ini mailarchive pop3_password is null!')
428+
429+ self.print_info("%s Start Connction pop3 %s:%s ..." %
430+ (time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime()),
431+ pop_server,pop_user))
432+
433+ pop = poplib.POP3(pop_server)
434+ pop.user(pop_user)
435+ pop.pass_(pop_password)
436+ num_messages = len(pop.list()[1])
437+ counter = 1
438+ for i in range(num_messages):
439+ #lines = ['',]
440+ #for j in pop.retr(i+1)[1]:
441+ # lines.append(j + os.linesep)
442+ #mes_text = ''.join(lines)
443+ mes_text = ''.join(['%s\n' % line for line in pop.retr(i+1)[1]])
444+ messageid = ''
445+ exception_flag = False
446+ try:
447+ msg = email.message_from_string(mes_text)
448+ messageid = msg['message-id']
449+ self.import_message(msg,author,mlid,db)
450+ except Exception, e:
451+ exception_flag = True
452+ self.print_error('Exception At Message-ID:'+messageid)
453+ self.print_error( e )
454+
455+ #if exception_flag == False:
456+ # self.print_info(" Import Message Success")
457+
458+
459+ # delete mail
460+ if pop_delete == 'all':
461+ pop.dele(i+1)
462+ self.print_info(" Delete MailServer Message ")
463+ elif pop_delete == 'imported':
464+ if exception_flag == False:
465+ pop.dele(i+1)
466+ self.print_info(" Delete MailServer Message ")
467+ else:
468+ pass
469+
470+ if counter > 10000:
471+ break
472+ counter = counter + 1
473+
474+ pop.quit()
475+
476+ #if handle_ta:
477+ db.commit()
478+ self.print_info("End Reciving. " )
479+
480+ def is_file(self,part ):
481+ """Return True:filename associated with the payload if present.
482+ """
483+ missing = object()
484+ filename = part.get_param('filename', missing, 'content-disposition')
485+ if filename is missing:
486+ filename = part.get_param('name', missing, 'content-disposition')
487+ if filename is missing:
488+ return False
489+ return True
490+
491+ def get_filename(self,part , failobj=None):
492+ """Return the filename associated with the payload if present.
493+
494+ The filename is extracted from the Content-Disposition header's
495+ `filename' parameter, and it is unquoted. If that header is missing
496+ the `filename' parameter, this method falls back to looking for the
497+ `name' parameter.
498+ """
499+ missing = object()
500+ filename = part.get_param('filename', missing, 'content-disposition')
501+ if filename is missing:
502+ filename = part.get_param('name', missing, 'content-disposition')
503+ if filename is missing:
504+ return failobj
505+
506+ errors='replace'
507+ fallback_charset='us-ascii'
508+ if isinstance(filename, tuple):
509+ rawval = unquote(filename[2])
510+ charset = filename[0] or 'us-ascii'
511+ try:
512+ return self.to_unicode(rawval, charset)
513+ except LookupError:
514+ # XXX charset is unknown to Python.
515+ return unicode(rawval, fallback_charset, errors)
516+ else:
517+ return self.decode_to_unicode(unquote(filename))
518+
--- plugins/mailarchiveplugin/branches/0.12/mailarchive/mailarchive.py (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/mailarchive.py (revision 805)
@@ -0,0 +1,618 @@
1+# -*- coding: utf-8 -*-
2+# MailArchive plugin
3+
4+from datetime import datetime
5+import urllib
6+import time
7+import calendar
8+import re
9+import os
10+import tempfile
11+import email.Errors
12+import email.Utils
13+import mailbox
14+import mimetypes
15+import email
16+#from email.Parser import Parser
17+from email.Header import decode_header
18+#from email.Utils import collapse_rfc2231_value
19+
20+#011
21+import pkg_resources
22+
23+from genshi.builder import tag
24+
25+
26+from trac.core import *
27+from trac.env import IEnvironmentSetupParticipant
28+#from trac.Search import ISearchSource, search_to_sql, shorten_result
29+from trac.search import ISearchSource, search_to_sql, shorten_result
30+
31+from trac.web import IRequestHandler
32+from trac.util import NaivePopen
33+from StringIO import StringIO
34+
35+
36+
37+from trac.wiki import wiki_to_html,wiki_to_oneliner, IWikiSyntaxProvider
38+from trac.util.html import html, Markup #0.10
39+from trac.web.chrome import add_link, add_stylesheet,add_ctxtnav, prevnext_nav, \
40+ INavigationContributor, ITemplateProvider
41+
42+#0.11 from trac.attachment import attachment_to_hdf, attachments_to_hdf, Attachment, AttachmentModule
43+from trac.attachment import AttachmentModule
44+
45+
46+#011 from trac.Timeline import ITimelineEventProvider #same
47+from trac.timeline.api import ITimelineEventProvider
48+
49+#011
50+from trac.util.translation import _
51+from trac.resource import *
52+from trac.mimeview.api import Context
53+
54+from trac.mimeview import *
55+#from trac.mimeview.api import Mimeview, IContentConverter #0.10
56+
57+#011
58+#from trac.perm import PermissionError, PermissionSystem, IPermissionPolicy
59+from trac.perm import IPermissionRequestor
60+from trac.resource import IResourceManager
61+from trac.attachment import ILegacyAttachmentPolicyDelegate
62+
63+#011
64+from trac.util.datefmt import to_timestamp, utc
65+
66+from trac.util.presentation import Paginator
67+
68+def get_author(fromname,fromaddr):
69+ author = fromname
70+ if fromname=='':
71+ if re.match('(.+?)@',fromaddr):
72+ author = re.match('(.+?)@',fromaddr).group(1)
73+ if author == None or author.strip() =='':
74+ author = '--'
75+ return author
76+
77+class Timeline(Component):
78+ implements(ITimelineEventProvider)
79+
80+ # ITimelineEventProvider methods
81+
82+ def get_timeline_filters(self, req):
83+ if 'MAILARCHIVE_VIEW' in req.perm:
84+ yield ('mailarchive', _(self.env.config.get('mailarchive', 'title','MailArchive')))
85+
86+ def get_timeline_events(self, req, start, stop, filters):
87+ if 'mailarchive' in filters:
88+ add_stylesheet(req, 'mailarchive/css/mailarchive.css')
89+
90+ db = self.env.get_db_cnx()
91+ mailarchive_realm = Resource('mailarchive')
92+ cursor = db.cursor()
93+
94+ cursor.execute("SELECT id,category as mlname,utcdate as localdate,"
95+ "fromname,fromaddr , subject FROM mailarc "
96+ "WHERE utcdate>=%s AND utcdate<=%s ",
97+ (to_timestamp(start), to_timestamp(stop)))
98+ for id,category,localdate, fromname, fromaddr,subject in cursor:
99+ #if 'WIKI_VIEW' not in req.perm('wiki', name):
100+ # continue
101+ author = get_author(fromname,fromaddr)
102+ #ctx = context('mailarchive', id)
103+
104+ resource = mailarchive_realm(id=id,version=None)
105+ if 'MAILARCHIVE_VIEW' not in req.perm(resource):
106+ continue
107+ yield ('mailarchive',
108+ datetime.fromtimestamp(localdate, utc),
109+ author or '--',
110+ (resource,(category,author,subject)))
111+
112+
113+ def render_timeline_event(self, context, field, event):
114+ mailarchive_page,(category,author,subject) = event[3]
115+ if field == 'url':
116+ return context.href.mailarchive(mailarchive_page.id, version=mailarchive_page.version)
117+ elif field == 'title':
118+ markup = tag(u'メールが ',category,u'に送信されました')
119+ return markup
120+ elif field == 'description':
121+ markup = tag(subject)
122+ return markup
123+
124+
125+class SearchProvider(Component):
126+ implements(ISearchSource)
127+
128+ # ISearchProvider methods
129+
130+ def get_search_filters(self, req):
131+ if 'MAILARCHIVE_VIEW' in req.perm:
132+ yield ('mailarchive', self.env.config.get('mailarchive', 'title','MailArchive'))
133+
134+
135+ def get_search_results(self, req, terms, filters):
136+ if 'mailarchive' in filters:
137+ db = self.env.get_db_cnx()
138+ sql_query, args = search_to_sql(db, ['m1.messageid','m1.subject','m1.fromname','m1.fromaddr','m1.text'],terms)
139+ cursor = db.cursor()
140+ cursor.execute("SELECT m1.id,m1.subject,m1.fromname,m1.fromaddr,m1.text,m1.utcdate as localdate "
141+ "FROM mailarc m1 "
142+ "WHERE "
143+ "" + sql_query, args)
144+ mailarchive_realm = Resource('mailarchive')
145+
146+ for id,subject,fromname,fromaddr, text,localdate in cursor:
147+ #if 'WIKI_VIEW' in req.perm('wiki', name):
148+ resource = mailarchive_realm(id=id,version=None)
149+ if 'MAILARCHIVE_VIEW' not in req.perm(resource):
150+ continue
151+
152+ yield (req.href.mailarchive(id),
153+ subject,
154+ datetime.fromtimestamp(localdate, utc),
155+ get_author(fromname,fromaddr),
156+ shorten_result(text, terms))
157+
158+
159+
160+class MailArchiveModule(Component):
161+ implements(ITemplateProvider,
162+ IRequestHandler,IEnvironmentSetupParticipant,INavigationContributor,
163+ IPermissionRequestor,ILegacyAttachmentPolicyDelegate,IResourceManager)
164+
165+ # INavigationContributor methods
166+
167+ def get_active_navigation_item(self, req):
168+ return 'mailarchive'
169+
170+ def get_navigation_items(self, req):
171+ if 'MAILARCHIVE_VIEW' in req.perm('mailarchive'):
172+ yield ('mainnav', 'mailarchive',
173+ tag.a(_('MailArchive'), href=req.href.mailarchive()))
174+
175+ # ITemplateProvider methods
176+
177+ def get_htdocs_dirs(self):
178+ return [('mailarchive',pkg_resources.resource_filename(__name__, 'htdocs'))]
179+
180+ def get_templates_dirs(self):
181+ return [pkg_resources.resource_filename(__name__, 'templates')]
182+
183+
184+ # IRequestHandler methods
185+
186+ def match_request(self, req):
187+ match = re.match(r'^/mailarchive(?:/(.*))?', req.path_info)
188+ if match:
189+ if match.group(1):
190+ req.args['messageid'] = match.group(1)
191+ return 1
192+
193+ def process_request(self, req):
194+ req.perm.assert_permission('MAILARCHIVE_VIEW')
195+ db = self.env.get_db_cnx()
196+
197+ messageid = req.args.get('messageid', '')
198+
199+ #id = req.args.get('id','')
200+ action = req.args.get('action', 'list')
201+
202+ if action == 'import':
203+ # brefore import lock db , in order to avoid import twice
204+ self.import_unixmails(req.remote_addr,db)
205+ # after import unlock db
206+ return self._render_list(req, db, False,False)
207+
208+ elif messageid != '':
209+ return self._render_view(req, db, messageid)
210+ else:
211+ # did the user ask for any special report?
212+ return self._render_list(req, db, False,False)
213+
214+ # IEnvironmentSetupParticipant methods
215+
216+ ienvironment_log = ""
217+ def environment_created(self):
218+ pass
219+
220+ def environment_needs_upgrade(self, db):
221+ cursor = db.cursor()
222+ try:
223+ cursor.execute("SELECT id FROM mailarc WHERE id='1'")
224+ except :
225+ db.rollback()
226+ self.log.debug('MailArchive environment_needs_upgrade')
227+ return True
228+
229+ return False
230+
231+
232+ def upgrade_environment(self, db):
233+ self.log.debug('MailArchive upgrade_environment')
234+
235+ sql = [
236+"""
237+CREATE TABLE mailarc (id integer,category text, messageid text ,
238+ utcdate integer, zoneoffset integer,
239+ subject text, fromname text, fromaddr text, header text, text text,
240+ threadroot text, threadparent text);
241+""",
242+"""
243+CREATE TABLE mailarc_category ( category text,mlid text ,yearmonth text ,count integer);
244+""",
245+"""
246+CREATE UNIQUE INDEX mailarc_messageid_idx ON mailarc (messageid)
247+""",
248+"""
249+CREATE INDEX mailarc_id_idx ON mailarc (id)
250+""",
251+"""
252+CREATE INDEX mailarc_category_idx ON mailarc (category)
253+""",
254+"""
255+CREATE INDEX mailarc_utcdate_idx ON mailarc (utcdate)
256+""",
257+"""
258+CREATE UNIQUE INDEX mailarc_category_category_idx ON mailarc_category (category)
259+""",
260+ ]
261+
262+
263+ #db = self.env.get_db_cnx()
264+ cursor = db.cursor()
265+ for s in sql:
266+ cursor.execute(s)
267+ self.log.debug('%s' % s)
268+
269+ #pass
270+
271+ def _get_category_href(self,req,category):
272+ return req.href.mailarchive()+'?category=%s' % category
273+ def _get_href(self,req,id):
274+ return req.href.mailarchive(id)
275+
276+ def _render_view(self, req, db, id):
277+ title, description, sql = ('ML名','MLの説明','select * from mailarc')
278+ #req.hdf['mailarc.action'] = 'view'
279+ data = {}
280+ data['action'] = 'view'
281+
282+ target_threadroot = ''
283+ cursor = db.cursor()
284+ cursor.execute("SELECT id,messageid,utcdate,zoneoffset,subject,fromname,fromaddr,text,threadroot FROM mailarc WHERE id=%s",(id,))
285+
286+ #messages = []
287+ for id,messageid,utcdate,zoneoffset,subject,fromname,fromaddr,text,threadroot in cursor:
288+ prefix ='mailarc'
289+
290+ #zone and date
291+ zone = ''
292+ if zoneoffset == '':
293+ zoneoffset = 0
294+ if zoneoffset >0:
295+ zone = ' +' + time.strftime('%H%M',time.gmtime(zoneoffset))
296+ elif zoneoffset < 0:
297+ zone = ' -' + time.strftime('%H%M',time.gmtime(-1*zoneoffset))
298+
299+ localdate = time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(utcdate+zoneoffset))
300+
301+ # from
302+ fromtext = ''
303+ fromaddr = fromaddr.replace('@',
304+ self.env.config.get('mailarchive', 'replaceat','@'))
305+ #if fromname=='':
306+ # fromtext = fromaddr
307+ #else:
308+ # fromtext = '%s (%s)' % (fromname,fromaddr)
309+ fromtext = get_author(fromname,fromaddr)
310+
311+ #subjectが空だとリンクにならない。
312+ if subject == None or subject.strip()=='':
313+ subject = '___'
314+
315+ message = {
316+ 'id':id,
317+ 'subject':subject,
318+ 'href':req.href.mailarchive(id),
319+ 'fromname':fromtext,
320+ 'fromaddr':fromaddr,
321+ 'senddate':localdate + zone,
322+ 'messageid':messageid
323+ }
324+ target_threadroot = threadroot
325+
326+ text = text.replace('@',self.env.config.get('mailarchive', 'replaceat','_at_') )
327+
328+ contentlines = text.splitlines()
329+ htmllines = ['',]
330+ for line in contentlines:
331+ if self.env.config.get('mailarchive', 'wikiview','enabled') == 'enabled':
332+ htmllines.append(wiki_to_oneliner(line, self.env,db,True,True,req))
333+ else:
334+ htmllines.append(Markup(Markup().escape(line).replace(' ','&nbsp;')))
335+
336+ content = Markup('<br/>').join(htmllines)
337+
338+ message['page_html'] = content
339+ #messages.append(message)
340+
341+ break
342+
343+ # Todo:Raise error when messsageid is wrong.
344+ # List attached files
345+ #req.perm.require('ATTACHMENT_VIEW')
346+ context = Context.from_request(req, Resource('mailarchive', str(id), None))
347+ #self.log.debug(context)
348+ data['attachments']=AttachmentModule(self.env).attachment_data(context)
349+ #self.log.debug(data['attachments'])
350+ #req.hdf['mailarc.attachments'] = attachments_to_hdf(self.env, req, db,
351+ # 'mailarchive', id)
352+
353+
354+ #if req.perm.has_permission('TICKET_APPEND'):
355+ #req.hdf['mailarc.attach_href'] = self.env.href.attachment('mailarchive',
356+ # id)
357+
358+ if 'mailarc_mails' in req.session:
359+ self.log.debug(req.session['mailarc_mails'])
360+ mails = req.session['mailarc_mails'].split()
361+ if str(id) in mails:
362+ idx = mails.index(str(id))
363+ if idx > 0:
364+ #add_ctxtnav(req, _('first'), req.href.mailarchive(mails[0]))
365+ add_link(req, _('prev'), req.href.mailarchive(mails[idx - 1]))
366+ if idx < len(mails) - 1:
367+ add_link(req, _('next'), req.href.mailarchive(mails[idx + 1]))
368+ #add_ctxtnav(req, _('last'), req.href.mailarchive(mails[-1]))
369+ add_link(req, _('up'), req.session['mailarc_category_href'])
370+ prevnext_nav(req,u'メール', u'リストに戻る')
371+
372+
373+ #if target_threadroot == '':
374+ # target_threadroot = messageid
375+
376+ ref_count=0
377+ reflist = []
378+ cursor.execute("SELECT id,messageid,utcdate,zoneoffset,subject,fromname,fromaddr,text,threadroot FROM mailarc WHERE messageid=%s or threadroot=%s ORDER BY utcdate",(target_threadroot,target_threadroot))
379+ for ref_id,ref_messageid,utcdate,zoneoffset,subject,fromname,fromaddr,text,threadroot in cursor:
380+ ref_count = ref_count +1
381+ #subjectが空だとリンクにならない。
382+ if subject == None or subject.strip()=='':
383+ subject = '___'
384+
385+ ref ={
386+ 'id':str(ref_id),
387+ 'subject':subject[:20],
388+ 'subject_alt':subject,
389+ 'fromname':get_author(fromname,fromaddr),
390+ 'date':time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(utcdate+zoneoffset)),
391+ 'href':''
392+ }
393+ if messageid == ref_messageid:
394+ pass
395+ else:
396+ ref['href'] =req.href.mailarchive(ref_id)
397+ reflist.append(ref)
398+
399+ add_stylesheet(req, 'mailarchive/css/mailarchive.css')
400+ data['reflist'] = reflist
401+ data['message'] = message
402+
403+ return 'maildetail.html', data, None
404+
405+ def month_add(self,year,month,add_month):
406+ month = month + add_month
407+ while month >12 or month <1:
408+ if month > 12:
409+ month = month - 12
410+ year = year + 1
411+ else :
412+ month = month + 12
413+ year = year - 1
414+
415+ # Internal methods
416+ def _render_list(self, req, db, thread_flag , month):
417+ target_category = req.args.get('category', '')
418+ #month = req.args.get('month', '')
419+ #this_month = time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(utcdate+zoneoffset))
420+
421+ data = {}
422+
423+ ids = ['',]
424+
425+ title, description, sql = ('ML名','MLの説明','select * from mailarc')
426+ #req.hdf['mailarc.mode'] = 'list'
427+ data['mode'] = 'list'
428+
429+ mesid_prefix = {}
430+
431+ cursor = db.cursor()
432+ cursor.execute("SELECT category,mlid,yearmonth,count FROM mailarc_category ORDER BY mlid,yearmonth DESC")
433+
434+ mls = []
435+ pre_mlid = ''
436+ for category,mlid,yearmonth,count in cursor:
437+ if target_category == '':
438+ target_category = category
439+
440+ category_item = {
441+ 'id': mlid + yearmonth,
442+ 'name': mlid,
443+ 'year': yearmonth[:4],
444+ 'month': yearmonth[4:],
445+ 'count': str(count),
446+ 'href': self._get_category_href(req,category)
447+ }
448+ if category == target_category:
449+ data['name'] = mlid
450+ data['year'] = yearmonth[:4]
451+ data['month'] = yearmonth[4:]
452+ category_item['href'] = ""
453+
454+ if pre_mlid != mlid:
455+ mls.append({'name':mlid,'yearmonths':[]})
456+ pre_mlid = mlid
457+ mls[-1]['yearmonths'].append(category_item)
458+
459+ attachments_list = {}
460+ cursor.execute("SELECT DISTINCT attachment.id as id ,mailarc.id as id2, utcdate FROM mailarc,attachment WHERE mailarc.category=%s AND CAST('mailarc.id' as text) = attachment.id AND attachment.type='mailarchive' ORDER BY utcdate",(target_category.encode('utf-8'),))
461+ for id,id2,utcdate in cursor:
462+ attachments_list[str(id)] = 1
463+
464+ thread_flag = True
465+ cursor.execute("SELECT id,messageid,utcdate,zoneoffset,subject,fromname,fromaddr,threadparent,threadroot FROM mailarc WHERE category=%s ORDER BY utcdate",(target_category.encode('utf-8'),))
466+
467+ #pagelize
468+ results = []
469+ for id,messageid,utcdate,zoneoffset,subject,fromname,fromaddr,thread_parent,thread_root in cursor:
470+ results.append((id,messageid,utcdate,zoneoffset,subject,fromname,fromaddr,thread_parent,thread_root))
471+ pagelized = self._pagelize_list(req,results,data)
472+
473+ #make message tree
474+ root_message = {
475+ 'children':[]
476+ }
477+ messageid_to_message = {'':root_message}
478+ for id,messageid,utcdate,zoneoffset,subject,fromname,fromaddr,thread_parent,thread_root in pagelized.items:
479+ #subjectが空だとリンクにならない。
480+ if subject == None or subject.strip()=='':
481+ subject = '___'
482+
483+ #date
484+ if zoneoffset == '':
485+ zoneoffset = 0
486+ localdate = time.strftime("%Y/%m/%d %H:%M:%S",time.gmtime(utcdate+zoneoffset))
487+ zone = ''
488+ if zoneoffset >0:
489+ zone = ' +' + time.strftime('%H%M',time.gmtime(zoneoffset))
490+ elif zoneoffset < 0:
491+ zone = ' -' + time.strftime('%H%M',time.gmtime(-1*zoneoffset))
492+
493+ message = {
494+ 'subject':subject,
495+ 'mail_href':req.href.mailarchive(id),
496+ 'fromname':fromname,
497+ 'senddate':localdate + zone,
498+ 'threadparent':thread_parent,
499+ 'threadroot':thread_root,
500+ 'attachment':0,
501+ 'children':[]
502+ }
503+ if fromname=='' and re.match('(.+?)@',fromaddr):
504+ message['fromname'] = re.match('(.+?)@',fromaddr).group(1)
505+
506+ if attachments_list.has_key(str(id)) :
507+ message['attachment'] = 1
508+
509+ #Search Parent
510+ messages = self._serach_parent(messageid_to_message,thread_parent)
511+ if messages.has_key('children'):
512+ messages['children'].append(message)
513+ #ソートを逆順にするにはappendでなくinsertを使うこと
514+ messageid_to_message[messageid] = message
515+
516+ ids.append(id)
517+
518+ idstext = ''.join(['%s ' % id for id in ids])
519+ self.log.debug("Idtext: %s" % idstext)
520+ req.session['mailarc_mails'] = idstext
521+ req.session['mailarc_category_href'] = self._get_category_href(req,target_category)
522+
523+ data['messages'] = root_message
524+ data['mls'] = mls;
525+
526+ add_stylesheet(req, 'mailarchive/css/mailarchive.css')
527+
528+ return 'mailarchive.html', data, None
529+
530+
531+ def _pagelize_list(self,req,results,data):
532+ # get page from req(default page = max_page)
533+ page = int(req.args.get('page', '-1'))
534+ num_item_per_page = int(self.env.config.get('mailarchive', 'items_page','50'))
535+ num_shown_pages = int(self.env.config.get('mailarchive', 'shown_pages','30'))
536+ if page == -1:
537+ results_temp = Paginator(results, 0, num_item_per_page)
538+ page = results_temp.num_pages
539+
540+ results = Paginator(results, page - 1, num_item_per_page)
541+
542+ pagedata = []
543+ data['page_results'] = results
544+ shown_pages = results.get_shown_pages(num_shown_pages)
545+ for shown_page in shown_pages:
546+ page_href = req.href.mailarchive(category=req.args.get('category',None),
547+ page=shown_page, noquickjump=1)
548+ pagedata.append([page_href, None, str(shown_page),
549+ 'page ' + str(shown_page)])
550+
551+ fields = ['href', 'class', 'string', 'title']
552+ results.shown_pages = [dict(zip(fields, p)) for p in pagedata]
553+
554+ results.current_page = {'href': None, 'class': 'current',
555+ 'string': str(results.page + 1),
556+ 'title':None}
557+
558+ if results.has_next_page:
559+ next_href = req.href.mailarchive(category=req.args.get('category',None),
560+ page=page + 1)
561+ add_link(req, 'next', next_href, _('Next Page'))
562+
563+ if results.has_previous_page:
564+ prev_href = req.href.mailarchive(category=req.args.get('category',None),
565+ page=page - 1)
566+ add_link(req, 'prev', prev_href, _('Previous Page'))
567+
568+ data['page_href'] = req.href.mailarchive(category=req.args.get('category',None))
569+ return results
570+
571+ def _serach_parent(self,messageid_to_message,thread_parent):
572+ parents = thread_parent.split(' ')
573+ for parent in parents:
574+ if messageid_to_message.has_key(parent) == True:
575+ self.log.debug('Thread:%s' % parent)
576+ return messageid_to_message[parent]
577+ return messageid_to_message['']
578+
579+
580+
581+ # IPermissionRequestor method
582+
583+ def get_permission_actions(self):
584+ return ['MAILARCHIVE_VIEW',
585+ ('MAILARCHIVE_ADMIN', ['MAILARCHIVE_VIEW']),
586+ ]
587+
588+ # ILegacyAttachmentPolicyDelegate methods
589+
590+ def check_attachment_permission(self, action, username, resource, perm):
591+ """ Respond to the various actions into the legacy attachment
592+ permissions used by the Attachment module. """
593+ if resource.parent.realm == 'mailarchive':
594+ if action == 'ATTACHMENT_VIEW':
595+ return 'MAILARCHIVE_VIEW' in perm(resource.parent)
596+ if action in ['ATTACHMENT_CREATE', 'ATTACHMENT_DELETE']:
597+ if 'MAILARCHIVE_ADMIN' in perm(resource.parent):
598+ return True
599+ else:
600+ return True # False
601+
602+ # IResourceManager methods
603+
604+ def get_resource_realms(self):
605+ yield 'mailarchive'
606+
607+ def get_resource_url(self, resource, href, **kwargs):
608+ return href.mailarchive(resource.id)
609+
610+ def get_resource_description(self, resource, format=None, context=None,
611+ **kwargs):
612+ if context:
613+ return tag.a('mail:'+resource.id, href=context.href.mailarchive(resource.id))
614+ else:
615+ return 'mail:'+resource.id
616+
617+
618+
--- plugins/mailarchiveplugin/branches/0.12/mailarchive/wikisyntax.py (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/wikisyntax.py (revision 805)
@@ -0,0 +1,72 @@
1+from trac.core import *
2+from trac import util
3+from trac.wiki import IWikiSyntaxProvider
4+
5+
6+class WikiSyntaxMail(Component):
7+ implements(IWikiSyntaxProvider)
8+
9+ # IWikiSyntaxProvider
10+
11+ def get_link_resolvers(self):
12+ return [('mail', self._format_link)]
13+
14+ def get_wiki_syntax(self):
15+ yield (r"!?\mail:([0-9]+)", # mail:123
16+ lambda x, y, z: self._format_link(x, 'mail', y[5:], y))
17+
18+ def _format_link(self, formatter, ns, target, label):
19+ cursor = formatter.db.cursor()
20+ cursor.execute("SELECT subject,id FROM mailarc WHERE id = %s" , (target,))
21+ row = cursor.fetchone()
22+ if row:
23+ subject = util.escape(util.shorten_line(row[0]))
24+ return '<a href="%s" title="%s">%s</a>' \
25+ % (formatter.href.mailarchive(row[1]), subject, label)
26+ else:
27+ return label
28+
29+class WikiSyntaxMl(Component):
30+ implements(IWikiSyntaxProvider)
31+
32+ # IWikiSyntaxProvider
33+
34+ def get_link_resolvers(self):
35+ return [('ml', self._format_link)]
36+
37+ def get_wiki_syntax(self):
38+ yield (r"!?\[(.+?)[ :]([0-9]+)\]", # [xxx 123] or [aaa:123]
39+ lambda x, y, z: self._format_link(x, 'ml', y[1:1], y))
40+
41+ def _format_link(self, formatter, ns, target, label):
42+ cursor = formatter.db.cursor()
43+ cursor.execute("SELECT subject,id FROM mailarc WHERE subject like '%s%%'" % label)
44+ row = cursor.fetchone()
45+ if row:
46+ subject = util.escape(util.shorten_line(row[0]))
47+ return '<a href="%s" title="%s">%s</a>' \
48+ % (formatter.href.mailarchive(row[1]), subject, label)
49+ else:
50+ return label
51+
52+class WikiSyntaxMessageId(Component):
53+ implements(IWikiSyntaxProvider)
54+
55+ def get_link_resolvers(self):
56+ return [('messageid', self._format_link)]
57+
58+ def get_wiki_syntax(self):
59+ yield (r"!?Message-ID:<(.+?)>", # Message-ID:<aaa>
60+ lambda x, y, z: self._format_link(x, 'messageid' ,y[12:-1],y))
61+
62+ def _format_link(self, formatter, ns, target, label):
63+ cursor = formatter.db.cursor()
64+ cursor.execute("SELECT subject,id FROM mailarc WHERE messageid = %s" , (target,))
65+ row = cursor.fetchone()
66+ if row:
67+ subject = util.escape(util.shorten_line(row[0]))
68+ return '<a href="%s" title="%s">%s</a>' \
69+ % (formatter.href.mailarchive(row[1]), subject, label)
70+ else:
71+ return label
72+
--- plugins/mailarchiveplugin/branches/0.12/mailarchive/test/maketestmail.py (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/test/maketestmail.py (revision 805)
@@ -0,0 +1,43 @@
1+# -*- coding: utf-8 -*-
2+# MailArchive plugin
3+
4+from datetime import datetime,timedelta
5+import uuid
6+
7+
8+now = datetime.now()
9+today = datetime.today()
10+
11+_mail_num = 1000
12+i = 0
13+
14+mail_address="testmail@example.com"
15+dt = datetime.now()
16+message_id = ''
17+
18+for i in range(0,_mail_num):
19+ last_message_id = message_id
20+ if i % 5 == 0:
21+ last_message_id = ''
22+ message_id = '%s%s'%(uuid.uuid4() , mail_address)
23+ dt = dt + timedelta(0,60)
24+ print "From - %s"%(dt.strftime('%a %b %d %H:%M:%S %Y')) #Mon Jun 30 14:29:49 2008
25+ print "Received: by 192.168.0.1 with HTTP; Sun, 29 Jun 2008 22:26:59 -0700 (PDT)"
26+ print "Message-ID: <%s>" %( message_id )
27+ print "Date: %s +0900"%(dt.strftime('%a, %d %b %Y %H:%M:%S')) #Mon, 30 Jun 2008 14:26:59 +0900
28+ print "From: %s"%(mail_address)
29+ print "To: %s"%(mail_address)
30+ print "Subject: TestII%s "%(str(i))
31+ if last_message_id != '':
32+ print "In-Reply-To: <%s>" %( last_message_id )
33+ print "MIME-Version: 1.0"
34+ print "Content-Type: text/plain; charset=ISO-8859-1"
35+ print "Content-Transfer-Encoding: 7bit"
36+ print "Content-Disposition: inline"
37+ print "Delivered-To: mailarchivetest@example.com"
38+ print ""
39+ print "This is Test Mail ."
40+ print ""
41+ print ""
42+
43+
--- plugins/mailarchiveplugin/branches/0.12/mailarchive/htdocs/css/mailarchive.css (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/htdocs/css/mailarchive.css (revision 805)
@@ -0,0 +1,24 @@
1+.thread_ul { padding-left: 20px; margin-left:0px;}
2+.thread_li { padding-left: 0px; margin-left:0px;}
3+.thread_subject_clip {
4+ padding-right:20px;
5+ background-image: url(../png/clip.png);
6+ background-repeat: no-repeat;
7+ background-position: right top;
8+}
9+.thread_from { font-size: 80%; color: #666}
10+.thread_senddate { font-size: 80%; color: #666}
11+#prefs ul {padding-left: 10px; margin:0px 3px; }
12+#prefs li {padding-left: 0px; margin:0px 3px; }
13+#prefs .prefs_from { color: #666}
14+#prefs {width:120px;}
15+* html #prefs { width: 14em } /* Set width only for IE */
16+
17+
18+/*
19+** Style used for displaying Mail icon in Timeline
20+*/
21+
22+.timeline dt.mailarchive a {
23+ background-image: url(../png/mail.png);
24+}
--- plugins/mailarchiveplugin/branches/0.12/mailarchive/templates/maildetail.html (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/templates/maildetail.html (revision 805)
@@ -0,0 +1,55 @@
1+<!DOCTYPE html
2+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4+<html xmlns="http://www.w3.org/1999/xhtml"
5+ xmlns:py="http://genshi.edgewall.org/"
6+ xmlns:xi="http://www.w3.org/2001/XInclude">
7+ <xi:include href="layout.html" />
8+ <xi:include href="macros.html" />
9+ <head>
10+ <title>MailArchive - $message.subject</title>
11+ <script type="text/javascript">
12+ jQuery(document).ready(function($) {
13+ $("#content").find("h1,h2,h3,h4,h5,h6").addAnchor("${_('Link to this section')}");
14+ });
15+ </script>
16+ </head>
17+
18+ <body>
19+ <div id="content" class="mailarchive">
20+
21+
22+ <form py:if="len(reflist) >=2" id="prefs" method="get" action="">
23+ <label >関連するメール:</label>
24+ <ul>
25+ <py:for each="refmail in reflist">
26+ <li>
27+ <py:choose test="refmail.href!=''">
28+ <py:when test="True">
29+ <a class="subject" href="$refmail.href" alt="$refmail.subject_alt">$refmail.subject</a>
30+ </py:when>
31+ <py:otherwise>
32+ <strong alt="$refmail.subject_alt">$refmail.subject</strong>
33+ </py:otherwise>
34+ </py:choose>
35+ <span class="prefs_from" >$refmail.fromname</span></li>
36+ </py:for>
37+ </ul>
38+ </form>
39+
40+ <h2>$message.subject</h2>
41+ <ul>
42+ <li>From:<a class="mail-link" href="mailto:$message.fromaddr"><span class="icon">$message.fromname</span></a></li>
43+ <li>Date:$message.senddate</li>
44+ </ul>
45+
46+ <div class="mailarcpage searchable">$message.page_html</div>
47+
48+ ${list_of_attachments(attachments,True)}
49+
50+</div>
51+
52+ </body>
53+</html>
54+
55+
--- plugins/mailarchiveplugin/branches/0.12/mailarchive/templates/mailarchive.html (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/templates/mailarchive.html (revision 805)
@@ -0,0 +1,83 @@
1+<!DOCTYPE html
2+ PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
3+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
4+<html xmlns="http://www.w3.org/1999/xhtml"
5+ xmlns:py="http://genshi.edgewall.org/"
6+ xmlns:xi="http://www.w3.org/2001/XInclude">
7+ <xi:include href="layout.html" />
8+ <xi:include href="macros.html" />
9+ <head>
10+ <title>${_('MailArchive')}</title>
11+ </head>
12+
13+ <body>
14+
15+ <div id="content" class="mailarc">
16+ <form id="prefs" method="get" action="">
17+ <py:for each="ml in mls">
18+ <div class="category_name">$ml.name</div>
19+ <ul>
20+ <py:for each="subitem in ml.yearmonths">
21+ <li class="category_li">
22+ <py:choose test="subitem.href">
23+ <py:when test="">
24+ <a href="$subitem.href">$subitem.year/$subitem.month ($subitem.count)</a>
25+ </py:when>
26+ <py:otherwise>
27+ $subitem.year/$subitem.month ($subitem.count)
28+ </py:otherwise>
29+ </py:choose>
30+ </li>
31+ </py:for>
32+ </ul>
33+ </py:for>
34+ </form>
35+
36+ <h2>
37+ $nameの$year年$month月のメール <span class="numresults" py:if="page_results">(${page_results.displayed_items()})</span>
38+ </h2>
39+
40+
41+<py:def function="mailarc_row(rows,depth)">
42+ <py:for each="message in rows">
43+ <li class="thread_li">
44+ <py:choose>
45+ <a py:when="message.attachment" class="thread_subject_clip"
46+ href="$message.mail_href">$message.subject</a>
47+ <a py:otherwise=""
48+ href="$message.mail_href">$message.subject</a>
49+ </py:choose>
50+ <br />
51+
52+ <span class="thread_from">$message.fromname</span>
53+ <span class="thread_senddate">$message.senddate</span>
54+
55+ <ul py:if="len(message.children)>0" class="thread_ul">
56+ ${mailarc_row(message.children,depth+1)}
57+ </ul>
58+ </li>
59+ </py:for>
60+</py:def>
61+
62+<xi:include py:with="paginator = page_results" href="page_index.html" />
63+<div id="mail_thread">
64+<ul py:if="len(messages.children)>0" class="thread_ul">
65+${mailarc_row(messages.children,0)}
66+</ul>
67+</div>
68+<xi:include py:with="paginator = page_results" href="page_index.html" />
69+
70+
71+
72+
73+
74+
75+
76+ <div id="help">
77+ </div>
78+
79+ </div>
80+ </body>
81+</html>
82+
83+
--- plugins/mailarchiveplugin/branches/0.12/mailarchive/__init__.py (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/mailarchive/__init__.py (revision 805)
@@ -0,0 +1,4 @@
1+# MailArchive module
2+from mailarchive import *
3+from wikisyntax import *
4+from mailarchiveadmin import *
\ No newline at end of file
--- plugins/mailarchiveplugin/branches/0.12/setup.py (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/setup.py (revision 805)
@@ -0,0 +1,16 @@
1+from setuptools import find_packages,setup
2+
3+setup(
4+ name='TracMailArchive', version='0.12.0.1',
5+ packages=find_packages(exclude=['*.tests*']),
6+ entry_points = """
7+ [trac.plugins]
8+ TracMailArchive = mailarchive
9+ """,
10+ package_data={'mailarchive': ['templates/*.html',
11+ 'htdocs/css/*.css',
12+ 'htdocs/png/*']},
13+ install_requires = [
14+ 'Trac>=0.12',
15+ ],
16+)
--- plugins/mailarchiveplugin/branches/0.12/license.txt (nonexistent)
+++ plugins/mailarchiveplugin/branches/0.12/license.txt (revision 805)
@@ -0,0 +1,28 @@
1+Copyright (c) 2007, Kazuya Hirobe.
2+All rights reserved.
3+
4+Redistribution and use in source and binary forms, with or without
5+modification, are permitted provided that the following conditions
6+are met:
7+
8+ 1. Redistributions of source code must retain the above copyright
9+ notice, this list of conditions and the following disclaimer.
10+ 2. Redistributions in binary form must reproduce the above copyright
11+ notice, this list of conditions and the following disclaimer in
12+ the documentation and/or other materials provided with the
13+ distribution.
14+ 3. The name of the author may not be used to endorse or promote
15+ products derived from this software without specific prior
16+ written permission.
17+
18+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS
19+OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
22+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
24+GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
26+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
27+OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
28+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Show on old repository browser