Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
inpos | 23adb48b90 | |
inpos | 5ac9c36546 | |
inpos | c98032ebe8 | |
inpos | d74db6bde5 | |
inpos | ee94d3a6d3 | |
inpos | 6812dd0b8d |
59
mech_imap.py
59
mech_imap.py
|
@ -9,7 +9,7 @@ from twisted.internet import protocol, ssl
|
|||
from twisted.mail import imap4
|
||||
|
||||
from serpent.config import conf
|
||||
from serpent.imap.mailbox import IMAPMailbox
|
||||
from serpent.imap.mbox import ExtendedMaildir
|
||||
from serpent.misc import IMAP_HDELIM, IMAP_MBOX_REG, IMAP_ACC_CONN_NUM
|
||||
from shutil import rmtree, move
|
||||
|
||||
|
@ -25,11 +25,19 @@ class IMAPUserAccount(object):
|
|||
else:
|
||||
IMAP_MBOX_REG[self.dir] = {}
|
||||
IMAP_MBOX_REG[self.dir][IMAP_ACC_CONN_NUM] = 0
|
||||
for m in conf.imap_auto_mbox:
|
||||
if m not in IMAP_MBOX_REG[self.dir].keys():
|
||||
for m in conf.imap_auto_mbox.keys():
|
||||
name = m
|
||||
if isinstance(m, unicode):
|
||||
m = m.encode('imap4-utf-7')
|
||||
IMAP_MBOX_REG[self.dir][m] = IMAPMailbox(os.path.join(self.dir, m))
|
||||
if m not in IMAP_MBOX_REG[self.dir].keys():
|
||||
<<<<<<< HEAD
|
||||
if isinstance(m, unicode):
|
||||
m = m.encode('imap4-utf-7')
|
||||
IMAP_MBOX_REG[self.dir][m] = ExtendedMaildir(os.path.join(self.dir, m))
|
||||
=======
|
||||
IMAP_MBOX_REG[self.dir][m] = self.create(m)
|
||||
IMAP_MBOX_REG[self.dir][m].setSpecial(conf.imap_auto_mbox[name])
|
||||
>>>>>>> refs/remotes/origin/sqlitedict
|
||||
IMAP_MBOX_REG[self.dir][m]._start_monitor()
|
||||
self.subscribe(m)
|
||||
|
||||
|
@ -37,7 +45,7 @@ class IMAPUserAccount(object):
|
|||
if isinstance(path, unicode):
|
||||
path = path.encode('imap4-utf-7')
|
||||
fullPath = os.path.join(self.dir, path)
|
||||
mbox = IMAPMailbox(fullPath)
|
||||
mbox = ExtendedMaildir(fullPath)
|
||||
mbox._start_monitor()
|
||||
return mbox
|
||||
|
||||
|
@ -54,7 +62,7 @@ class IMAPUserAccount(object):
|
|||
if path in os.listdir(self.dir):
|
||||
return self.create(path)
|
||||
else:
|
||||
raise imap4.NoSuchMailbox, path
|
||||
return None
|
||||
|
||||
def addMailbox(self, name, mbox = None):
|
||||
if mbox:
|
||||
|
@ -71,18 +79,22 @@ class IMAPUserAccount(object):
|
|||
if subpath not in IMAP_MBOX_REG[self.dir].keys():
|
||||
try:
|
||||
IMAP_MBOX_REG[self.dir][subpath] = self._getMailbox(IMAP_HDELIM.join(paths[:accum]))
|
||||
IMAP_MBOX_REG[self.dir][subpath].subscribe()
|
||||
except imap4.MailboxCollision:
|
||||
pass
|
||||
IMAP_MBOX_REG[self.dir][subpath].hasChildren()
|
||||
IMAP_MBOX_REG[self.dir][pathspec] = self._getMailbox(pathspec)
|
||||
IMAP_MBOX_REG[self.dir][pathspec].hasNoChildren()
|
||||
IMAP_MBOX_REG[self.dir][pathspec].subscribe()
|
||||
return IMAP_MBOX_REG[self.dir][pathspec]
|
||||
|
||||
def delete(self, pathspec):
|
||||
if isinstance(pathspec, unicode):
|
||||
pathspec = pathspec.encode('imap4-utf-7')
|
||||
if pathspec in conf.imap_auto_mbox:
|
||||
if pathspec in conf.imap_auto_mbox.keys():
|
||||
raise imap4.MailboxException, pathspec
|
||||
if pathspec not in IMAP_MBOX_REG[self.dir].keys():
|
||||
raise imap4.MailboxException("No such mailbox")
|
||||
raise imap4.NoSuchMailbox, pathspec
|
||||
inferiors = self._inferiorNames(pathspec)
|
||||
if r'\Noselect' in IMAP_MBOX_REG[self.dir][pathspec].getFlags():
|
||||
# Check for hierarchically inferior mailboxes with this one
|
||||
|
@ -98,7 +110,7 @@ class IMAPUserAccount(object):
|
|||
return True
|
||||
|
||||
def rename(self, oldname, newname):
|
||||
if oldname in conf.imap_auto_mbox:
|
||||
if oldname in conf.imap_auto_mbox.keys():
|
||||
raise imap4.MailboxException, oldname
|
||||
if isinstance(oldname, unicode):
|
||||
oldname = oldname.encode('imap4-utf-7')
|
||||
|
@ -111,11 +123,15 @@ class IMAPUserAccount(object):
|
|||
if new in IMAP_MBOX_REG[self.dir].keys():
|
||||
raise imap4.MailboxCollision, new
|
||||
for (old, new) in inferiors:
|
||||
IMAP_MBOX_REG[self.dir][old]._stop_monitor()
|
||||
move(os.path.join(self.dir, old), os.path.join(self.dir, new))
|
||||
IMAP_MBOX_REG[self.dir][new] = IMAP_MBOX_REG[self.dir][old]
|
||||
IMAP_MBOX_REG[self.dir][new].path = os.path.join(self.dir, new)
|
||||
IMAP_MBOX_REG[self.dir][new].path_msg_info = os.path.join(self.dir, conf.imap_msg_info)
|
||||
IMAP_MBOX_REG[self.dir][new].path_mbox_info = os.path.join(self.dir, conf.imap_mbox_info)
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
IMAP_MBOX_REG[self.dir][new].open_flags()
|
||||
IMAP_MBOX_REG[self.dir][new]._start_monitor()
|
||||
>>>>>>> refs/remotes/origin/sqlitedict
|
||||
del IMAP_MBOX_REG[self.dir][old]
|
||||
return True
|
||||
|
||||
|
@ -124,19 +140,27 @@ class IMAPUserAccount(object):
|
|||
name = name.encode('imap4-utf-7')
|
||||
if name in IMAP_MBOX_REG[self.dir].keys():
|
||||
IMAP_MBOX_REG[self.dir][name].subscribe()
|
||||
return True
|
||||
#raise imap4.NoSuchMailbox, name
|
||||
|
||||
def unsubscribe(self, name):
|
||||
if name in conf.imap_auto_mbox:
|
||||
raise imap4.MailboxException, name
|
||||
if name in conf.imap_auto_mbox.keys():
|
||||
return False
|
||||
# raise imap4.MailboxException, name
|
||||
if isinstance(name, unicode):
|
||||
name = name.encode('imap4-utf-7')
|
||||
if name in IMAP_MBOX_REG[self.dir].keys():
|
||||
IMAP_MBOX_REG[self.dir][name].unsubscribe()
|
||||
return True
|
||||
#raise imap4.NoSuchMailbox, name
|
||||
|
||||
def isSubscribed(self, name):
|
||||
if isinstance(name, unicode):
|
||||
name = name.encode('imap4-utf-7')
|
||||
if name in IMAP_MBOX_REG[self.dir].keys():
|
||||
return IMAP_MBOX_REG[self.dir][name].is_subscribed()
|
||||
else:
|
||||
raise imap4.NoSuchMailbox, name
|
||||
|
||||
def _inferiorNames(self, name):
|
||||
name_l = name.split(IMAP_HDELIM)
|
||||
|
@ -283,6 +307,14 @@ class IMAPServerProtocol(imap4.IMAP4Server):
|
|||
self.sendPositiveResponse(tag, 'STATUS complete')
|
||||
def __ebStatus(self, failure, tag, box):
|
||||
self.sendBadResponse(tag, 'STATUS %s failed: %s' % (box, str(failure.value)))
|
||||
def _cbListWork(self, mailboxes, tag, sub, cmdName):
|
||||
for (name, box) in mailboxes:
|
||||
if not sub or self.account.isSubscribed(name):
|
||||
flags = box.getMboxFlags()
|
||||
delim = box.getHierarchicalDelimiter()
|
||||
resp = (imap4.DontQuoteMe(cmdName), map(imap4.DontQuoteMe, flags), delim, name.encode('imap4-utf-7'))
|
||||
self.sendUntaggedResponse(imap4.collapseNestedLists(resp))
|
||||
self.sendPositiveResponse(tag, '%s completed' % (cmdName,))
|
||||
|
||||
################################################################################
|
||||
|
||||
|
@ -297,6 +329,7 @@ class SerpentIMAPFactory(protocol.Factory):
|
|||
cert = ssl.PrivateCertificate.loadPEM(tls_data)
|
||||
contextFactory = cert.options()
|
||||
p = IMAPServerProtocol(contextFactory = contextFactory)
|
||||
p.setTimeout(conf.imap_connection_timeout)
|
||||
if conf.tls:
|
||||
p.canStartTLS = True
|
||||
p.IDENT = '%s ready' % conf.SRVNAME
|
||||
|
|
|
@ -6,9 +6,10 @@ conf = Config()
|
|||
conf.VERSION = '0.1.0'
|
||||
conf.SRVNAME = 'Serpent'
|
||||
conf.srv_version = '%s %s' % (conf.SRVNAME, conf.VERSION)
|
||||
conf.imap_connection_timeout = 120
|
||||
conf.local_domains = ['dom.lan'] # Список доменов, для которых будет приниматься почта
|
||||
conf.tls = True
|
||||
conf.tls_pem = './serpent.pem'
|
||||
conf.tls_pem = u'./serpent.pem'
|
||||
conf.smtp_open_relay = False # Разрешить ли пересылку откуда угодно куда угодно
|
||||
conf.smtp_email_delim = '@'
|
||||
conf.smtp_header = '''from [{sender_ip}] (helo={sender_host})
|
||||
|
@ -28,9 +29,17 @@ conf.smtp_email_tls_required = True
|
|||
|
||||
conf.imap_SENT = 'Sent'
|
||||
conf.imap_TRASH = 'Trash'
|
||||
conf.imap_subscribed = '.subscribed'
|
||||
conf.imap_JUNK = 'Junk'
|
||||
conf.imap_ARCHIVE = 'Archive'
|
||||
conf.imap_DRAFTS = 'Drafts'
|
||||
conf.imap_msg_info = 'msg_info.db'
|
||||
conf.imap_mbox_info = 'mbox_info.db'
|
||||
conf.imap_auto_mbox = ['INBOX', 'Sent', 'Trash']
|
||||
conf.imap_auto_mbox = {'INBOX': '\\INBOX',
|
||||
conf.imap_SENT: '\\Sent',
|
||||
conf.imap_TRASH: '\\Trash',
|
||||
conf.imap_JUNK: '\\Junk',
|
||||
conf.imap_ARCHIVE: '\\Archive',
|
||||
conf.imap_DRAFTS: '\\Drafts'
|
||||
}
|
||||
conf.imap_expunge_on_close = True
|
||||
conf.imap_check_new_interval = 10.0 # Период проверки новых сообщений в ящике
|
|
@ -60,16 +60,19 @@ class SmtpFileStore(object):
|
|||
|
||||
class MailDirStore(object):
|
||||
def __init__(self):
|
||||
from serpent.imap import mailbox
|
||||
self.mbox = mailbox
|
||||
from serpent.imap import mbox
|
||||
from mailbox import MaildirMessage
|
||||
self.mbox = mbox
|
||||
self.mbox.MaildirMessage = MaildirMessage
|
||||
def deliver(self, user, message):
|
||||
mdir = os.path.join(conf.app_dir, conf.maildir_user_path % user)
|
||||
if not os.path.exists(mdir):
|
||||
os.makedirs(mdir)
|
||||
inbox = os.path.join(mdir, 'INBOX')
|
||||
mailbox = self.mbox.IMAPMailbox(inbox)
|
||||
mailbox = self.mbox.ExtendedMaildir(inbox)
|
||||
msg = self.mbox.MaildirMessage(message)
|
||||
try:
|
||||
mailbox.addMessage(message, [IMAP_FLAGS['RECENT']])
|
||||
mailbox.add(msg, [])
|
||||
return True
|
||||
except:
|
||||
raise
|
||||
|
|
|
@ -1,315 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from twisted.mail import maildir, imap4
|
||||
from twisted.mail.smtp import rfc822date
|
||||
from twisted.internet import inotify
|
||||
from twisted.python import filepath
|
||||
|
||||
from zope.interface import implements
|
||||
from threading import Thread
|
||||
|
||||
import random
|
||||
import email
|
||||
from pickle import load, dump
|
||||
from StringIO import StringIO
|
||||
import os
|
||||
|
||||
from serpent.config import conf
|
||||
from serpent import misc
|
||||
|
||||
from sqlitedict import SqliteDict
|
||||
|
||||
class LoopingTask(Thread):
|
||||
def __init__(self, func, event, interval):
|
||||
Thread.__init__(self)
|
||||
self.func = func
|
||||
self.interval = interval
|
||||
self.stopped = event
|
||||
|
||||
def run(self):
|
||||
while not self.stopped.wait(self.interval):
|
||||
self.func()
|
||||
|
||||
class SerpentAppendMessageTask(maildir._MaildirMailboxAppendMessageTask):
|
||||
|
||||
def moveFileToNew(self):
|
||||
while True:
|
||||
newname = os.path.join(self.mbox.path, "new", maildir._generateMaildirName())
|
||||
try:
|
||||
self.osrename(self.tmpname, newname)
|
||||
break
|
||||
except OSError, (err, estr):
|
||||
import errno
|
||||
# if the newname exists, retry with a new newname.
|
||||
if err != errno.EEXIST:
|
||||
self.fail()
|
||||
newname = None
|
||||
break
|
||||
if newname is not None:
|
||||
self.mbox.lastadded = newname
|
||||
self.defer.callback(None)
|
||||
self.defer = None
|
||||
|
||||
|
||||
|
||||
class ExtendedMaildir(maildir.MaildirMailbox):
|
||||
def __iter__(self):
|
||||
return iter(self.list)
|
||||
|
||||
def __getitem__(self, i):
|
||||
return self.list[i]
|
||||
|
||||
class IMAPMailbox(ExtendedMaildir):
|
||||
implements(imap4.IMailbox, imap4.ICloseableMailbox)
|
||||
|
||||
AppendFactory = SerpentAppendMessageTask
|
||||
|
||||
def __init__(self, path):
|
||||
maildir.initializeMaildir(path)
|
||||
self.listeners = []
|
||||
self.path = path
|
||||
self.msg_info = SqliteDict(os.path.join(path, conf.imap_msg_info))
|
||||
self.mbox_info = SqliteDict(os.path.join(path, conf.imap_mbox_info))
|
||||
self.lastadded = None
|
||||
self.__check_flags_()
|
||||
|
||||
def _start_monitor(self):
|
||||
self.notifier = inotify.INotify()
|
||||
self.notifier.startReading()
|
||||
self.notifier.watch(filepath.FilePath(os.path.join(self.path, 'new')),
|
||||
callbacks=[self._new_files])
|
||||
self.notifier.watch(filepath.FilePath(os.path.join(self.path,'cur')),
|
||||
callbacks=[self._new_files])
|
||||
|
||||
def _new_files(self, wo, path, code):
|
||||
if code == inotify.IN_MOVED_TO or code == inotify.IN_DELETE:
|
||||
for l in self.listeners:
|
||||
l.newMessages(self.getMessageCount(), self.getRecentCount())
|
||||
|
||||
def __check_flags_(self):
|
||||
if 'subscribed' not in self.mbox_info.keys(): self.mbox_info['subscribed'] = False
|
||||
if 'uidvalidity' not in self.mbox_info.keys(): self.mbox_info['uidvalidity'] = random.randint(0, 2**32)
|
||||
if 'uidnext' not in self.mbox_info.keys(): self.mbox_info['uidnext'] = 1
|
||||
self.mbox_info.commit(blocking=False)
|
||||
l = [l for l in self.__msg_list_()]
|
||||
for i in l:
|
||||
fn = i.split('/')[-1]
|
||||
if fn not in msg_info.keys():
|
||||
val1 = {'uid': self.getUIDNext()}
|
||||
if i.split('/')[-2] == 'new':
|
||||
val1['flags'] = []
|
||||
else:
|
||||
val1['flags'] = [misc.IMAP_FLAGS['SEEN']]
|
||||
self.msg_info[fn] = val1
|
||||
self.msg_info.commit(blocking=False)
|
||||
|
||||
def subscribe(self):
|
||||
self.mbox_info['subscribed'] = True
|
||||
self.mbox_info.commit(blocking=False)
|
||||
|
||||
def unsubscribe(self):
|
||||
self.mbox_info['subscribed'] = False
|
||||
self.mbox_info.commit(blocking=False)
|
||||
|
||||
def is_subscribed(self):
|
||||
return self.mbox_info['subscribed']
|
||||
|
||||
def __count_flagged_msgs_(self, flag):
|
||||
val1 = [0 for fn in self.msg_info.keys() if flag in self.msg_info[fn]['flags']]
|
||||
return len(val1)
|
||||
|
||||
def getHierarchicalDelimiter(self):
|
||||
return misc.IMAP_HDELIM
|
||||
|
||||
def getFlags(self):
|
||||
return misc.IMAP_FLAGS.values()
|
||||
|
||||
def getMessageCount(self):
|
||||
val1 = [0 for fn in self.msg_info.keys() if misc.IMAP_FLAGS['DELETED'] not in self.msg_info[fn]['flags']]
|
||||
return len(val1)
|
||||
|
||||
def getRecentCount(self):
|
||||
return self.__count_flagged_msgs_(misc.IMAP_FLAGS['RECENT'])
|
||||
|
||||
def getUnseenCount(self):
|
||||
return self.getMessageCount() - self.__count_flagged_msgs_(misc.IMAP_FLAGS['SEEN'])
|
||||
|
||||
def isWriteable(self):
|
||||
return True
|
||||
|
||||
def getUIDValidity(self):
|
||||
return self.mbox_info['uidvalidity']
|
||||
|
||||
def getUIDNext(self):
|
||||
un = self.mbox_info['uidnext']
|
||||
self.mbox_info['uidnext'] += 1
|
||||
self.mbox_info.commit(blocking=False)
|
||||
return un
|
||||
|
||||
def getUID(self, num):
|
||||
return num
|
||||
|
||||
def addMessage(self, message, flags = (), date = None):
|
||||
return self.appendMessage(message).addCallback(self._cbAddMessage, flags)
|
||||
|
||||
def _cbAddMessage(self, obj, flags):
|
||||
path = self.lastadded
|
||||
self.lastadded = None
|
||||
fn = path.split('/')[-1]
|
||||
self.msg_info[fn] = {'uid': self.getUIDNext(), 'flags': flags}
|
||||
self.msg_info.commit(blocking=False)
|
||||
if misc.IMAP_FLAGS['SEEN'] in flags and path.split('/')[-2] != 'cur':
|
||||
new_path = os.path.join(self.path, 'cur', fn)
|
||||
os.rename(path, new_path)
|
||||
|
||||
def __msg_list_(self):
|
||||
a = []
|
||||
for m in os.listdir(os.path.join(self.path, 'new')):
|
||||
a.append(os.path.join(self.path, 'new', m))
|
||||
for m in os.listdir(os.path.join(self.path, 'cur')):
|
||||
a.append(os.path.join(self.path, 'cur', m))
|
||||
return a
|
||||
|
||||
def _seqMessageSetToSeqDict(self, messageSet):
|
||||
if not messageSet.last:
|
||||
messageSet.last = self.getMessageCount()
|
||||
|
||||
seqMap = {}
|
||||
msgs = self.__msg_list_()
|
||||
for messageNum in messageSet:
|
||||
if messageNum > 0 and messageNum <= self.getMessageCount():
|
||||
seqMap[messageNum] = msgs[messageNum - 1]
|
||||
return seqMap
|
||||
|
||||
def fetch(self, messages, uid):
|
||||
return [[seq, MaildirMessage(seq,
|
||||
file(filename, 'rb').read(),
|
||||
self.msg_info[filename.split('/')[-1]]['flags'],
|
||||
rfc822date())]
|
||||
for seq, filename in self.__fetch_(messages, uid).iteritems()]
|
||||
def __fetch_(self, messages, uid):
|
||||
if uid:
|
||||
messagesToFetch = {}
|
||||
if not messages.last:
|
||||
messages.last = self.mbox_info['uidnext']
|
||||
fn_uid = dict((fn, self.msg_info[fn]['uid']) for fn in self.msg_info.keys())
|
||||
for uid in messages:
|
||||
if uid in fn_uid.values():
|
||||
for name, _id in fn_uid.iteritems():
|
||||
if uid == _id:
|
||||
if os.path.exists(os.path.join(self.path,'new', name)):
|
||||
messagesToFetch[uid] = os.path.join(self.path,'new', name)
|
||||
elif os.path.exists(os.path.join(self.path,'cur', name)):
|
||||
messagesToFetch[uid] = os.path.join(self.path,'cur', name)
|
||||
else:
|
||||
messagesToFetch = self._seqMessageSetToSeqDict(messages)
|
||||
return messagesToFetch
|
||||
def store(self, messages, flags, mode, uid):
|
||||
d = {}
|
||||
for _id, path in self.__fetch_(messages, uid).iteritems():
|
||||
filename = path.split('/')[-1]
|
||||
if mode < 0:
|
||||
old_f = self.msg_info[filename]['flags']
|
||||
self.msg_info[filename]['flags'] = list(set(old_f).difference(set(flags)))
|
||||
if misc.IMAP_FLAGS['SEEN'] in flags and path.split('/')[-2] != 'new':
|
||||
new_path = os.path.join(self.path, 'new', filename)
|
||||
os.rename(path, new_path)
|
||||
elif mode == 0:
|
||||
self.msg_info[filename]['flags'] = flags
|
||||
elif mode > 0:
|
||||
old_f = self.msg_info[filename]['flags']
|
||||
self.msg_info[filename]['flags'] = list(set(old_f).union(set(flags)))
|
||||
if misc.IMAP_FLAGS['SEEN'] in flags and path.split('/')[-2] != 'cur':
|
||||
new_path = os.path.join(self.path, 'cur', filename)
|
||||
os.rename(path, new_path)
|
||||
d[_id] = self.msg_info[filename]['flags']
|
||||
msg_info.commit(blocking=False)
|
||||
return d
|
||||
|
||||
def expunge(self):
|
||||
uids = []
|
||||
for path in self.__msg_list_():
|
||||
fn = path.split('/')[-1]
|
||||
if fn not in self.msg_info.keys():
|
||||
continue
|
||||
uid = self.msg_info[fn]['uid']
|
||||
if misc.IMAP_FLAGS['DELETED'] in self.msg_info[fn]['flags']:
|
||||
os.remove(path)
|
||||
del self.msg_info[fn]
|
||||
uids.append(uid)
|
||||
self.msg_info.commit(blocking=False)
|
||||
return uids
|
||||
|
||||
def addListener(self, listener):
|
||||
self.listeners.append(listener)
|
||||
return True
|
||||
|
||||
def removeListener(self, listener):
|
||||
self.listeners.remove(listener)
|
||||
return True
|
||||
|
||||
def requestStatus(self, names):
|
||||
return imap4.statusRequestHelper(self, names)
|
||||
|
||||
def destroy(self):
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
self.notifier.stopReading()
|
||||
self.notifier.loseConnection()
|
||||
if conf.imap_expunge_on_close:
|
||||
l = self.expunge()
|
||||
|
||||
class MaildirMessagePart(object):
|
||||
implements(imap4.IMessagePart)
|
||||
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
self.data = str(message)
|
||||
|
||||
def getHeaders(self, negate, *names):
|
||||
if not names:
|
||||
names = self.message.keys()
|
||||
|
||||
headers = {}
|
||||
if negate:
|
||||
for header in self.message.keys():
|
||||
if header.upper() not in names:
|
||||
headers[header.lower()] = self.message.get(header, '')
|
||||
else:
|
||||
for name in names:
|
||||
headers[name.lower()] = self.message.get(name, '')
|
||||
|
||||
return headers
|
||||
|
||||
def getBodyFile(self):
|
||||
return StringIO(self.message.get_payload())
|
||||
|
||||
def getSize(self):
|
||||
return len(self.data)
|
||||
|
||||
def isMultipart(self):
|
||||
return self.message.is_multipart()
|
||||
|
||||
def getSubPart(self, part):
|
||||
return MaildirMessagePart(self.message.get_payload(part))
|
||||
|
||||
class MaildirMessage(MaildirMessagePart):
|
||||
implements(imap4.IMessage)
|
||||
|
||||
def __init__(self, uid, message, flags, date):
|
||||
MaildirMessagePart.__init__(self, message)
|
||||
self.uid = uid
|
||||
self.message = email.message_from_string(message)
|
||||
self.flags = flags
|
||||
self.date = date
|
||||
|
||||
|
||||
def getUID(self):
|
||||
return self.uid
|
||||
|
||||
def getFlags(self):
|
||||
return self.flags
|
||||
|
||||
def getInternalDate(self):
|
||||
return self.date
|
|
@ -0,0 +1,37 @@
|
|||
from mailbox import Maildir
|
||||
import os
|
||||
|
||||
class ExtendedMaildir(Maildir):
|
||||
def set_flags(self, key, flags):
|
||||
sflags = sorted(flags)
|
||||
if sflags == self.get_flags(key): return True
|
||||
subpath = self._lookup(key)
|
||||
info = '2,' + ''.join(sflags)
|
||||
oldpath = os.path.join(self._path, subpath)
|
||||
newsubdir = os.path.split(subpath)[0]
|
||||
newname = key + self.colon + info
|
||||
if 'S' in sflags and newsubdir == 'new':
|
||||
newsubdir = 'cur'
|
||||
if 'S' not in sflags and newsubdir == 'cur':
|
||||
newsubdir = 'new'
|
||||
newpath = os.path.join(self._path, newsubdir, newname)
|
||||
if hasattr(os, 'link'):
|
||||
os.link(oldpath, newpath)
|
||||
os.remove(oldpath)
|
||||
else:
|
||||
os.rename(oldpath, newpath)
|
||||
self._toc[key] = os.path.join(newsubdir, newname)
|
||||
def get_flags(self, key):
|
||||
subpath = self._lookup(key)
|
||||
_, name = os.path.split(subpath)
|
||||
info = name.split(self.colon)[-1]
|
||||
if info.startswith('2,'):
|
||||
return info[2:]
|
||||
else:
|
||||
return ''
|
||||
def add_flag(self, key, flag):
|
||||
self.set_flags(key, ''.join(set(self.get_flags(key)) | set(flag)))
|
||||
def remove_flag(self, key, flag):
|
||||
if flag not in self.get_flags(key): return True
|
||||
if self.get_flags(key):
|
||||
self.set_flags(key, ''.join(set(self.get_flags(key)) - set(flag)))
|
|
@ -3,12 +3,20 @@ MSG_ACTIVE = 0
|
|||
MSG_FROZEN = 1
|
||||
|
||||
IMAP_FLAGS = {
|
||||
'SEEN': '\\Seen',
|
||||
'FLAGGED': '\\Flagged',
|
||||
'ANSWERED': '\\Answered',
|
||||
'RECENT': '\\Recent',
|
||||
'DELETED': '\\Deleted',
|
||||
'DRAFT': '\\Draft'
|
||||
'S': '\\Seen',
|
||||
'F': '\\Flagged',
|
||||
'P': '\\Passed',
|
||||
'R': '\\Replied',
|
||||
'T': '\\Trashed',
|
||||
'D': '\\Draft'
|
||||
}
|
||||
MBOX_FLAGS = {
|
||||
'NOINFERIORS': '\\Noinferiors',
|
||||
'NOSELECT': '\\Noselect',
|
||||
'MARKED': '\\Marked',
|
||||
'UNMARKED': '\\Unmarked',
|
||||
'HASCHILDREN': '\\HasChildren',
|
||||
'HASNOCHILDREN': '\\HasNoChildren'
|
||||
}
|
||||
IMAP_HDELIM = '.'
|
||||
IMAP_ACC_CONN_NUM = '...ConnectionNumber...'
|
||||
|
|
Loading…
Reference in New Issue