[Zope3-checkins] CVS: Zope3/src/zope/app/mail - maildir.py:1.1
Marius Gedminas
mgedmin@codeworks.lt
Wed, 21 May 2003 06:52:53 -0400
Update of /cvs-repository/Zope3/src/zope/app/mail
In directory cvs.zope.org:/tmp/cvs-serv7148/src/zope/app/mail
Added Files:
maildir.py
Log Message:
Read-write access for Maildir folders. First implementation with lots of
XXXes.
=== Added File Zope3/src/zope/app/mail/maildir.py ===
##############################################################################
#
# Copyright (c) 2003 Zope Corporation and Contributors.
# All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
"""Read/write access to Maildir folders.
XXX check exception types
$Id: maildir.py,v 1.1 2003/05/21 10:52:52 mgedmin Exp $
"""
import os
import stat
import socket
import time
from zope.interface import implements, classProvides
from zope.app.interfaces.mail import IMaildirFactory, IMaildir
from zope.app.interfaces.mail import IMaildirMessageWriter
__metaclass__ = type
class Maildir:
"""See zope.app.interfaces.mail.IMaildir"""
classProvides(IMaildirFactory)
implements(IMaildir)
def __init__(self, path, create=False):
"See zope.app.interfaces.mail.IMaildirFactory"
self.path = path
def isdir(path):
return stat.S_ISDIR(os.stat(path)[0])
def exists(path):
return os.access(path, os.F_OK)
subdir_cur = os.path.join(path, 'cur')
subdir_new = os.path.join(path, 'new')
subdir_tmp = os.path.join(path, 'tmp')
if create and not exists(path):
os.mkdir(path)
os.mkdir(subdir_cur)
os.mkdir(subdir_new)
os.mkdir(subdir_tmp)
maildir = True
else:
maildir = (isdir(subdir_cur) and isdir(subdir_new)
and isdir(subdir_tmp))
if not maildir:
raise ValueError('%s is not a Maildir folder' % path)
def __iter__(self):
"See zope.app.interfaces.mail.IMaildir"
join = os.path.join
subdir_cur = join(self.path, 'cur')
subdir_new = join(self.path, 'new')
new_messages = [join(subdir_new, x) for x in os.listdir(subdir_new)]
cur_messages = [join(subdir_cur, x) for x in os.listdir(subdir_cur)]
# XXX http://www.qmail.org/man/man5/maildir.html says:
# "It is a good idea for readers to skip all filenames in new
# and cur starting with a dot. Other than this, readers
# should not attempt to parse filenames."
return iter(new_messages + cur_messages)
def newMessage(self):
"See zope.app.interfaces.mail.IMaildir"
# XXX http://www.qmail.org/man/man5/maildir.html says, that the first
# step of the delivery process should be a chdir. Chdirs and
# threading do not mix. Is that chdir really necessary?
join = os.path.join
subdir_tmp = join(self.path, 'tmp')
subdir_new = join(self.path, 'new')
pid = os.getpid()
host = socket.gethostname()
counter = 0
while 1:
timestamp = int(time.time())
unique = '%d.%d.%s' % (timestamp, pid, host)
filename = join(subdir_tmp, unique)
try:
os.stat(filename)
except OSError, e:
# XXX How can I distinguish ENOENT from other errors?
break
else:
counter += 1
if counter >= 1000: # XXX hardcoded magic number
raise RuntimeError('Failed to create unique file name in %s,'
' are we under a DoS attack?' % subdir_tmp)
# XXX maildir.html (see above) says I should sleep for 2
# seconds, not 1
time.sleep(1)
return MaildirMessageWriter(filename, join(subdir_new, unique))
class MaildirMessageWriter:
"""See zope.app.interfaces.mail.IMaildirMessageWriter"""
implements(IMaildirMessageWriter)
def __init__(self, filename, new_filename):
self._filename = filename
self._new_filename = new_filename
self._fd = open(filename, 'w')
self._closed = False
self._aborted = False
def write(self, data):
self._fd.write(data)
def writelines(self, lines):
self._fd.writelines(lines)
def commit(self):
if self._closed and self._aborted:
raise AssertionError('Cannot commit, message already aborted')
elif not self._closed:
self._closed = True
self._aborted = False
self._fd.close()
os.rename(self._filename, self._new_filename)
# XXX the same maildir.html says it should be a link, followed by
# unlink. But Win32 does not necessarily have hardlinks!
def abort(self):
if not self._closed:
self._closed = True
self._aborted = True
self._fd.close()
os.unlink(self._filename)
# XXX should there be a __del__ that does abort()?