Флаги

master
inpos 2016-06-10 00:10:51 +03:00
parent 3b43af2b2f
commit 5e23c30797
4 changed files with 118 additions and 33 deletions

View File

@ -25,11 +25,13 @@ 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():
IMAP_MBOX_REG[self.dir][m] = self.create(m)
IMAP_MBOX_REG[self.dir][m].setSpecial(conf.imap_auto_mbox[name])
IMAP_MBOX_REG[self.dir][m]._start_monitor()
self.subscribe(m)
@ -54,7 +56,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,15 +73,19 @@ 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.NoSuchMailbox, pathspec
@ -98,7 +104,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,13 +117,13 @@ class IMAPUserAccount(object):
if new in IMAP_MBOX_REG[self.dir].keys():
raise imap4.MailboxCollision, new
for (old, new) in inferiors:
m = IMAP_MBOX_REG[self.dir][old]
del IMAP_MBOX_REG[self.dir][old]
for l in m.listeners: m.listeners.remove(l)
m.close()
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] = self._getMailbox(new)
IMAP_MBOX_REG[self.dir][new].subscribe()
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].open_flags()
IMAP_MBOX_REG[self.dir][new]._start_monitor()
del IMAP_MBOX_REG[self.dir][old]
return True
def subscribe(self, name):
@ -129,8 +135,9 @@ class IMAPUserAccount(object):
#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():
@ -141,7 +148,10 @@ class IMAPUserAccount(object):
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)
@ -288,6 +298,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,))
################################################################################
@ -302,6 +320,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

View File

@ -6,6 +6,7 @@ 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 = u'./serpent.pem'
@ -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 # Период проверки новых сообщений в ящике

View File

@ -68,11 +68,14 @@ class IMAPMailbox(ExtendedMaildir):
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.open_flags()
self.lastadded = None
self.__check_flags_()
def open_flags(self):
self.msg_info = SqliteDict(os.path.join(self.path, conf.imap_msg_info))
self.mbox_info = SqliteDict(os.path.join(self.path, conf.imap_mbox_info))
def _start_monitor(self):
self.notifier = inotify.INotify()
self.notifier.startReading()
@ -81,6 +84,10 @@ class IMAPMailbox(ExtendedMaildir):
self.notifier.watch(filepath.FilePath(os.path.join(self.path,'cur')),
callbacks=[self._new_files])
def _stop_monitor(self):
self.notifier.stopReading()
self.notifier.loseConnection()
def _new_files(self, wo, path, code):
if code == inotify.IN_MOVED_TO or code == inotify.IN_DELETE:
for l in self.listeners:
@ -88,6 +95,8 @@ class IMAPMailbox(ExtendedMaildir):
def __check_flags_(self):
if 'subscribed' not in self.mbox_info.keys(): self.mbox_info['subscribed'] = False
if 'flags' not in self.mbox_info.keys(): self.mbox_info['flags'] = []
if 'special' not in self.mbox_info.keys(): self.mbox_info['special'] = ''
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)
@ -121,15 +130,53 @@ class IMAPMailbox(ExtendedMaildir):
def getHierarchicalDelimiter(self):
return misc.IMAP_HDELIM
def setSpecial(self, special):
self.mbox_info['special'] = special
self.mbox_info.commit(blocking=False)
def getFlags(self):
return misc.IMAP_FLAGS.values()
return sorted(misc.IMAP_FLAGS.values())
def getMboxFlags(self):
f = list(self.mbox_info['flags'])
if self.mbox_info['special'] != '': f.append(self.mbox_info['special'])
return f
def addFlag(self, flag):
self.mbox_info['flags'] = list(set(self.mbox_info['flags']).union([flag]))
self.mbox_info.commit(blocking=False)
def removeFlag(self, flag):
self.mbox_info['flags'] = list(set(self.mbox_info['flags']).difference([flag]))
self.mbox_info.commit(blocking=False)
def hasChildren(self):
flags = self.getFlags()
if misc.MBOX_FLAGS['HASCHILDREN'] not in flags:
self.addFlag(misc.MBOX_FLAGS['HASCHILDREN'])
if misc.MBOX_FLAGS['HASNOCHILDREN'] in flags:
self.removeFlag(misc.MBOX_FLAGS['HASNOCHILDREN'])
def hasNoChildren(self):
flags = self.getFlags()
if misc.MBOX_FLAGS['HASNOCHILDREN'] not in flags:
self.addFlag(misc.MBOX_FLAGS['HASNOCHILDREN'])
if misc.MBOX_FLAGS['HASCHILDREN'] in flags:
self.removeFlag(misc.MBOX_FLAGS['HASCHILDREN'])
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'])
c = 0
for fn in self.msg_info.keys():
if misc.IMAP_FLAGS['RECENT'] in self.msg_info[fn]['flags']:
c += 1
info = self.msg_info[fn]
info['flags'] = set(info['flags']).difference(set([misc.IMAP_FLAGS['RECENT']]))
self.msg_info[fn] = info
self.msg_info.commit(blocking=False)
return c
def getUnseenCount(self):
return self.getMessageCount() - self.__count_flagged_msgs_(misc.IMAP_FLAGS['SEEN'])
@ -209,16 +256,20 @@ class IMAPMailbox(ExtendedMaildir):
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)))
old_f = self.msg_info[filename]
old_f['flags'] = list(set(old_f['flags']).difference(set(flags)))
self.msg_info[filename] = old_f
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
old_f = self.msg_info[filename]
old_f['flags'] = flags
self.msg_info[filename] = old_f
elif mode > 0:
old_f = self.msg_info[filename]['flags']
self.msg_info[filename]['flags'] = list(set(old_f).union(set(flags)))
old_f = self.msg_info[filename]
old_f['flags'] = list(set(old_f['flags']).union(set(flags)))
self.msg_info[filename] = old_f
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)
@ -255,12 +306,10 @@ class IMAPMailbox(ExtendedMaildir):
pass
def close(self):
self.notifier.stopReading()
self.notifier.loseConnection()
if len(self.listeners) == 0:
self._stop_monitor()
if conf.imap_expunge_on_close:
self.expunge()
self.mbox_info.close()
self.msg_info.close()
class MaildirMessagePart(object):
implements(imap4.IMessagePart)

View File

@ -10,6 +10,14 @@ IMAP_FLAGS = {
'DELETED': '\\Deleted',
'DRAFT': '\\Draft'
}
MBOX_FLAGS = {
'NOINFERIORS': '\\Noinferiors',
'NOSELECT': '\\Noselect',
'MARKED': '\\Marked',
'UNMARKED': '\\Unmarked',
'HASCHILDREN': '\\HasChildren',
'HASNOCHILDREN': '\\HasNoChildren'
}
IMAP_HDELIM = '.'
IMAP_ACC_CONN_NUM = '...ConnectionNumber...'
IMAP_MBOX_REG = {}