## $Id: folderops.py,v 1.21 2001/11/07 11:26:48 kjetilja Exp $
## Folder status flags
STATUS_READ = '-'
STATUS_UNREAD = ' '
STATUS_FORW = 'f'
STATUS_REPL = 'r'
import os.path, marshal
def build_folder_tree(dir, names, tree):
for name in names:
# Don't follow any symlinks
if os.path.islink(name):
continue
absname = os.path.join(dir, name)
# Check if this is a file or directory
if os.path.isdir(absname) and name[-4:] == '.sbd':
# Find all files in this dir
files = os.listdir(absname)
name = name[:-4]
# Make sure this folder exists before searching the subfolder
if os.path.exists(os.path.join(dir, '.' + name + '.idx')):
tree[name] = {}
build_folder_tree(absname, files, tree[name])
#else:
# print 'error finding %s' % \
# os.path.join(dir, '.' + name + '.idx')
elif name[0] == '.' and name[-4:] == '.idx':
# Append mail folder to tree
name = name[1:-4]
if not tree.has_key(name):
tree[name] = None
return tree
##
## Function get_active_folders (folder path)
##
## Construct folder list from the disk.
##
##
def get_active_folders(path):
# Find all folder index files
try:
# Find all files in this dir
files = os.listdir(path)
ftree = build_folder_tree(path, files, {})
except:
return None
else:
return ftree
##
## Function create_foldernames(ftree):
##
## This is to change FolderTree list (such as self.ftree) to FolderNames,
## i.e. add up parentDir to childDir as: "parentDir/childDir"
##
def create_foldernames(ftree):
if len(ftree)==0: return
keys = ftree.keys()
foldernames = keys [:] # [:] is to strong copy list
for k in keys:
if ftree[k]: # is a directory, not mail folder
tmp = create_foldernames (ftree[k])
for i in range(len(tmp)):
tmp[i] = '%s/%s' % (k, tmp[i])
foldernames = foldernames + tmp
return foldernames
##
## Function move_messages (prefs instance, src fld, dst fld, msg instance)
##
## Physically move messages from one folder to another.
##
##
def move_messages(src, dst, msgs):
unread_d = copy_messages (src, dst, msgs)
unread_s = del_messages (src, msgs)
# Return a tuple containing the number of unread messages in
# the source and destination folder
return (unread_s, unread_d)
##
## Function copy_messages (src fld, dst fld, msg instance)
##
## Physically copy messages from one folder to another.
##
##
def copy_messages(src, dst, msgs):
fs = open(src) # Source message file
fd = open(dst, "a") # Dest message file
di = get_index_from_pathname(dst)
fdi = open(di, "r+") # Dest index
fldcache = {}
sh = marshal.load(fdi)
for idx in msgs:
# Copy the complete message to the dest folder
start = int(idx[1])
stop = int(idx[2])
fs.seek(start, 0)
pos_start = int(fd.tell())
fd.write( fs.read(stop-start) )
pos_end = int(fd.tell())
fldcache[start] = stop
# Update the dest folder index for the dest folder in the dest
# 0-epoch 1-start 2-stop 3-status 4-from 5-subject 6-time
sh[str(pos_start)] = [ idx[3], str(pos_end), idx[5], idx[4], idx[0] ]
# Dest index file and folder are now finished
fdi.seek(0, 0)
marshal.dump(sh, fdi)
fd.close()
fdi.close()
# Record the number of unread messages in the destination folder
unread_d = unread(sh)
return unread_d
##
## Function del_messages (prefs instance, src fld, dst fld, msg instance)
##
## Physically delete messages from one folder.
##
##
def del_messages(src, msgs):
fs = open(src) # Source message file
si = get_index_from_pathname(src) # Source index path
fsi = open(si, "r+") # Source index file
fldcache = {}
for idx in msgs:
# Find out where the messages starts and stops
start = int(idx[1])
stop = int(idx[2])
fldcache[start] = stop
# Update the src folder file, omitting deleted messages.
# We write a new src file but do not include the message
# boundaries that are listed in 'fldcache'
newfs = open(src+".new", "w")
fs.seek(0, 0)
e = fldcache.keys()
e.sort()
cur = 0
for ent in e:
newfs.write( fs.read(ent-cur) )
cur = fldcache[ent]
fs.seek(cur, 0)
# Omitting messages completed, now write the rest
newfs.write( fs.read() )
newfs.close()
fs.close()
# Rename the updated folder with the original folder name
os.rename(src+".new", src)
# Last, update the source index file
e = fldcache.keys()
e.sort()
sh = marshal.load(fsi)
f = map( lambda x: int(x), sh.keys() )
f.sort()
# If we are deleting all folder entries, just reset the contents of
# the index file to an empty dictionary
if e == f:
fsi.seek(0, 0)
marshal.dump({}, fsi)
fsi.close()
return 0
# Delete cached entries
for i in e:
f.remove(i)
# Rebuild the index by updating relative positions between messages
newsh = {}
start = 0
for i in f:
offset = int(sh[str(i)][1]) - i
newsh[str(start)] = sh[str(i)]
newsh[str(start)][1] = str(start + offset)
start = start + offset
# Dump the updated index on disk
fsi.seek(0, 0)
marshal.dump(newsh, fsi)
fsi.close()
# Record the number of unread messages in the source folder
unread_s = unread(newsh)
return unread_s
##
## Function create_folder_index (pathname, file start position)
##
## Create an index file of a mail folder.
##
##
def create_folder_index(pathname, start=0):
import pygmymailbox, string
# Make sure all the dirs exists
path = '/'
dirs = string.split(os.path.dirname(pathname), '/')
for dir in dirs:
path = os.path.join(path, dir)
# Do we need to make this dir?
if not os.path.exists(path):
#print 'create_folder_index - making dir:', path
os.mkdir(path)
# Retrieve the folder name information
f = open(pathname)
idx = get_index_from_pathname(pathname)
# Cache the index file in memory to decrease number of disk-writes
try:
sh = marshal.load( open(idx) )
except:
sh = {}
mb = pygmymailbox.PygmyMailbox(f, start)
# Loop over the mailbox, extract header information
while 1:
m = mb.next()
if m is None:
break
# Get the From: field (some From headers span more than one line)
frm = get_from(m)
# Get the Subject: field
subject = get_subject(m)
# Get the Date: field
date = date_to_epoch(m)
# Put entries into index file
sh[str(int(m.fp.start))] = [ STATUS_UNREAD, str(int(m.fp.stop)),
subject, frm, date ]
f.close()
# Marshal the index and store it on disk
marshal.dump(sh, open(idx, "w"))
# Return the number of unread messages in the folder
return unread(sh)
##
## Helper functions for the indexer
##
def get_from(message):
import mimify, string
# Get the From: field (some From headers span more than one line)
frm = message.getheader('from')
if frm != None:
t = string.replace(frm, '\012', '')
message.__delitem__('from')
message.__setitem__('from', t)
name, addr = message.getaddr('from')
return mimify.mime_decode_header(name or ""), addr
def get_subject(message):
import mimify, string
# Get the Subject: field
subject = mimify.mime_decode_header(message.getheader('subject') or "")
# Remove problematic characters like newline
subject = string.replace(subject, '\n', '')
subject = string.replace(subject, '\r', '')
return subject
def date_to_epoch(message):
import time, rfc822
# Get the Date: field
date = message.getdate_tz('date') or ""
if date != "":
# Convert to epoch value
try:
date = rfc822.mktime_tz(date)
except:
date = time.time()
else:
# If we cannot parse the date, insert current epoch
date = time.time()
# Make an integer since the float conversion is locale specific
return str(int(date))
##
## Function num_unread (foldername)
##
## Number of messages in a folder (typed numbers).
##
##
def unread(fld):
num = 0
for n in fld.keys():
if fld[n][0] == STATUS_UNREAD:
num = num + 1
return num
def num_unread(pathname):
sh = fetch_folder_index(pathname)
return unread(sh)
def num_readunread(pathname):
sh = fetch_folder_index(pathname)
return len(sh.keys()), unread(sh)
def num_msgs(pathname):
sh = fetch_folder_index(pathname)
return len(sh.keys())
##
## Function update_folder_index (path, foldername)
##
## New messages need to be indexed in an existing folder.
##
##
def update_folder_index(pathname):
sh = fetch_folder_index(pathname)
f = map(lambda x: int(x), sh.keys())
f.sort()
# Need this one in case the dict is empty at startup
if len(f) == 0:
start = 0
else:
start = int( sh[str(f[-1])][1] )
# Recreate the folder index at the given start position
return create_folder_index(pathname, start)
##
## Function update_folder_index_status (path, foldername,
## file start, status)
##
## Update the status field in an index file.
##
##
def update_folder_index_status(pathname, start, update=STATUS_READ):
# Read folder index from disk
index = get_index_from_pathname(pathname)
fi = open(index, "r+")
sh = marshal.load( fi )
sh[str(start)][0] = update
fi.seek(0, 0)
marshal.dump(sh, fi)
fi.close()
return unread(sh)
##
## Function update_folder_index_status_multiple (path, msg)
##
## Update the status field for multiple messages in an index file.
##
##
def update_folder_index_status_multiple(path, msg):
# Read folder index from disk
index = get_index_from_pathname(path)
fi = open(index, "r+")
sh = marshal.load( fi )
for start, update in msg:
sh[str(start)][0] = update
fi.seek(0, 0)
marshal.dump(sh, fi)
fi.close()
return unread(sh)
##
## Function fetch_folder_index (path, foldername)
##
## Fetch folder index file contents.
##
##
def fetch_folder_index(pathname):
# Read folder index from disk and unmarshal it
index = get_index_from_pathname(pathname)
sh = marshal.load(open(index))
# Return the result
return sh
##
## Function check_index_consistency (prefs instance)
##
## Make index files consistent with folder contents.
##
##
def check_index_consistency(path, ftree):
from posix import stat
# Ensure that the index files are updated
for fname in ftree.keys():
tf = stat(path+"/"+fname)[8]
ti = stat(path+"/."+fname+".idx")[8]
# Check if the message folder is newer than the index
if tf > ti:
# Remove old index
os.remove(path+"/."+fname+".idx")
# Create new index -- will lose status but may save the msgs
create_folder_index(os.path.join(path, fname))
# Check subtree if any
subtree = ftree[fname]
if subtree:
check_index_consistency(os.path.join(path, fname + '.sbd'),
subtree)
##
## Function empty_trash (prefs instance)
##
## Empty the 'trash' folder.
##
##
def empty_trash(p):
# Zero the folder file
open(p.folders+"/trash", "w").close()
# Dump an empty dict in the index file
marshal.dump({}, open(p.folders+"/.trash.idx", "w") )
##
## Method get_folder_path ()
##
def get_folder_path(path, folder):
import string
real_path = ''
folders = os.path.dirname(folder)
for f in string.split(folders, '/'):
if f != '':
real_path = os.path.join(real_path, f + '.sbd')
real_path = os.path.join(path, real_path)
return real_path
def get_folder_pathname(path, folder):
real_path = get_folder_path(path, folder)
name = os.path.basename(folder)
pathname = os.path.join(real_path, name)
return pathname
def get_index_from_pathname(pathname):
path = os.path.dirname(pathname)
name = os.path.basename(pathname)
index = os.path.join(path, '.' + name + '.idx')
return index
def folder_expanded(path, folder):
realpath = get_folder_path(path, os.path.join(folder, ''))
if os.path.isfile(os.path.join(realpath, '.expanded')):
return 1
return 0
def expand_folder(path, folder, state=1):
realpath = get_folder_path(path, os.path.join(folder, ''))
pathname = os.path.join(realpath, '.expanded')
if state:
# Touch file
f = open(pathname, 'w')
f.close()
else:
# Remove file
try:
os.unlink(pathname)
except: pass
syntax highlighted by Code2HTML, v. 0.9.1