offlineimap/offlineimap/repository/Maildir.py

177 lines
7.6 KiB
Python

# Maildir repository support
# Copyright (C) 2002 John Goerzen
# <jgoerzen@complete.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
from Base import BaseRepository
from offlineimap import folder, imaputil
from offlineimap.ui import UIBase
from mailbox import Maildir
import os
from stat import *
class MaildirRepository(BaseRepository):
def __init__(self, reposname, account):
"""Initialize a MaildirRepository object. Takes a path name
to the directory holding all the Maildir directories."""
BaseRepository.__init__(self, reposname, account)
self.root = self.getlocalroot()
self.folders = None
self.ui = UIBase.getglobalui()
self.debug("MaildirRepository initialized, sep is " + repr(self.getsep()))
self.folder_atimes = []
# Create the top-level folder if it doesn't exist
if not os.path.isdir(self.root):
os.mkdir(self.root, 0700)
def _append_folder_atimes(self, foldername):
p = os.path.join(self.root, foldername)
new = os.path.join(p, 'new')
cur = os.path.join(p, 'cur')
f = p, os.stat(new)[ST_ATIME], os.stat(cur)[ST_ATIME]
self.folder_atimes.append(f)
def restore_folder_atimes(self):
if not self.folder_atimes:
return
for f in self.folder_atimes:
t = f[1], os.stat(os.path.join(f[0], 'new'))[ST_MTIME]
os.utime(os.path.join(f[0], 'new'), t)
t = f[2], os.stat(os.path.join(f[0], 'cur'))[ST_MTIME]
os.utime(os.path.join(f[0], 'cur'), t)
def getlocalroot(self):
return os.path.expanduser(self.getconf('localfolders'))
def debug(self, msg):
self.ui.debug('maildir', msg)
def getsep(self):
return self.getconf('sep', '.').strip()
def makefolder(self, foldername):
self.debug("makefolder called with arg " + repr(foldername))
# Do the chdir thing so the call to makedirs does not make the
# self.root directory (we'd prefer to raise an error in that case),
# but will make the (relative) paths underneath it. Need to use
# makedirs to support a / separator.
self.debug("Is dir? " + repr(os.path.isdir(foldername)))
if self.getsep() == '/':
for invalid in ['new', 'cur', 'tmp', 'offlineimap.uidvalidity']:
for component in foldername.split('/'):
assert component != invalid, "When using nested folders (/ as a separator in the account config), your folder names may not contain 'new', 'cur', 'tmp', or 'offlineimap.uidvalidity'."
assert foldername.find('./') == -1, "Folder names may not contain ../"
assert not foldername.startswith('/'), "Folder names may not begin with /"
oldcwd = os.getcwd()
os.chdir(self.root)
# If we're using hierarchical folders, it's possible that sub-folders
# may be created before higher-up ones. If this is the case,
# makedirs will fail because the higher-up dir already exists.
# So, check to see if this is indeed the case.
if (self.getsep() == '/' or self.getconfboolean('existsok', 0) or foldername == '.') \
and os.path.isdir(foldername):
self.debug("makefolder: %s already is a directory" % foldername)
# Already exists. Sanity-check that it's not a Maildir.
for subdir in ['cur', 'new', 'tmp']:
assert not os.path.isdir(os.path.join(foldername, subdir)), \
"Tried to create folder %s but it already had dir %s" %\
(foldername, subdir)
else:
self.debug("makefolder: calling makedirs %s" % foldername)
os.makedirs(foldername, 0700)
self.debug("makefolder: creating cur, new, tmp")
for subdir in ['cur', 'new', 'tmp']:
os.mkdir(os.path.join(foldername, subdir), 0700)
# Invalidate the cache
self.folders = None
os.chdir(oldcwd)
def deletefolder(self, foldername):
self.ui.warn("NOT YET IMPLEMENTED: DELETE FOLDER %s" % foldername)
def getfolder(self, foldername):
if self.config.has_option('Repository ' + self.name, 'restoreatime') and self.config.getboolean('Repository ' + self.name, 'restoreatime'):
self._append_folder_atimes(foldername)
return folder.Maildir.MaildirFolder(self.root, foldername,
self.getsep(), self,
self.accountname, self.config)
def _getfolders_scandir(self, root, extension = None):
self.debug("_GETFOLDERS_SCANDIR STARTING. root = %s, extension = %s" \
% (root, extension))
# extension willl only be non-None when called recursively when
# getsep() returns '/'.
retval = []
# Configure the full path to this repository -- "toppath"
if extension == None:
toppath = root
else:
toppath = os.path.join(root, extension)
self.debug(" toppath = %s" % toppath)
# Iterate over directories in top.
for dirname in os.listdir(toppath) + ['.']:
self.debug(" *** top of loop")
self.debug(" dirname = %s" % dirname)
if dirname in ['cur', 'new', 'tmp', 'offlineimap.uidvalidity']:
self.debug(" skipping this dir (Maildir special)")
# Bypass special files.
continue
fullname = os.path.join(toppath, dirname)
self.debug(" fullname = %s" % fullname)
if not os.path.isdir(fullname):
self.debug(" skipping this entry (not a directory)")
# Not a directory -- not a folder.
continue
foldername = dirname
if extension != None:
foldername = os.path.join(extension, dirname)
if (os.path.isdir(os.path.join(fullname, 'cur')) and
os.path.isdir(os.path.join(fullname, 'new')) and
os.path.isdir(os.path.join(fullname, 'tmp'))):
# This directory has maildir stuff -- process
self.debug(" This is a maildir folder.")
self.debug(" foldername = %s" % foldername)
if self.config.has_option('Repository ' + self.name, 'restoreatime') and self.config.getboolean('Repository ' + self.name, 'restoreatime'):
self._append_folder_atimes(foldername)
retval.append(folder.Maildir.MaildirFolder(self.root, foldername,
self.getsep(), self, self.accountname,
self.config))
if self.getsep() == '/' and dirname != '.':
# Check sub-directories for folders.
retval.extend(self._getfolders_scandir(root, foldername))
self.debug("_GETFOLDERS_SCANDIR RETURNING %s" % \
repr([x.getname() for x in retval]))
return retval
def getfolders(self):
if self.folders == None:
self.folders = self._getfolders_scandir(self.root)
return self.folders