Step 1 of rearranging per r129
This commit is contained in:
@ -1,2 +0,0 @@
|
||||
import ui, folder, repository, mbnames, threadutil
|
||||
|
@ -1,289 +0,0 @@
|
||||
# Base folder 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
import __main__
|
||||
from threading import *
|
||||
from offlineimap import threadutil
|
||||
from offlineimap.threadutil import InstanceLimitedThread
|
||||
|
||||
class BaseFolder:
|
||||
def getname(self):
|
||||
"""Returns name"""
|
||||
return self.name
|
||||
|
||||
def suggeststhreads(self):
|
||||
"""Returns true if this folder suggests using threads for actions;
|
||||
false otherwise. Probably only IMAP will return true."""
|
||||
return 0
|
||||
|
||||
def waitforthread(self):
|
||||
"""For threading folders, waits until there is a resource available
|
||||
before firing off a thread. For all others, returns immediately."""
|
||||
pass
|
||||
|
||||
def getcopyinstancelimit(self):
|
||||
"""For threading folders, returns the instancelimitname for
|
||||
InstanceLimitedThreads."""
|
||||
raise NotImplementedException
|
||||
|
||||
def storesmessages(self):
|
||||
"""Should be true for any backend that actually saves message bodies.
|
||||
(Almost all of them). False for the LocalStatus backend. Saves
|
||||
us from having to slurp up messages just for localstatus purposes."""
|
||||
return 1
|
||||
|
||||
def getvisiblename(self):
|
||||
return self.name
|
||||
|
||||
def getroot(self):
|
||||
"""Returns the root of the folder, in a folder-specific fashion."""
|
||||
return self.root
|
||||
|
||||
def getsep(self):
|
||||
"""Returns the separator for this folder type."""
|
||||
return self.sep
|
||||
|
||||
def getfullname(self):
|
||||
if self.getroot():
|
||||
return self.getroot() + self.getsep() + self.getname()
|
||||
else:
|
||||
return self.getname()
|
||||
|
||||
def isuidvalidityok(self, remotefolder):
|
||||
raise NotImplementedException
|
||||
|
||||
def getuidvalidity(self):
|
||||
raise NotImplementedException
|
||||
|
||||
def saveuidvalidity(self, newval):
|
||||
raise NotImplementedException
|
||||
|
||||
def cachemessagelist(self):
|
||||
"""Reads the message list from disk or network and stores it in
|
||||
memory for later use. This list will not be re-read from disk or
|
||||
memory unless this function is called again."""
|
||||
raise NotImplementedException
|
||||
|
||||
def getmessagelist(self):
|
||||
"""Gets the current message list.
|
||||
You must call cachemessagelist() before calling this function!"""
|
||||
raise NotImplementedException
|
||||
|
||||
def getmessage(self, uid):
|
||||
"""Returns the content of the specified message."""
|
||||
raise NotImplementedException
|
||||
|
||||
def savemessage(self, uid, content, flags):
|
||||
"""Writes a new message, with the specified uid.
|
||||
If the uid is < 0, the backend should assign a new uid and return it.
|
||||
|
||||
If the backend cannot assign a new uid, it returns the uid passed in
|
||||
WITHOUT saving the message.
|
||||
|
||||
IMAP backend should be the only one that can assign a new uid.
|
||||
|
||||
If the uid is > 0, the backend should set the uid to this, if it can.
|
||||
If it cannot set the uid to that, it will save it anyway.
|
||||
It will return the uid assigned in any case.
|
||||
"""
|
||||
raise NotImplementedException
|
||||
|
||||
def getmessageflags(self, uid):
|
||||
"""Returns the flags for the specified message."""
|
||||
raise NotImplementedException
|
||||
|
||||
def savemessageflags(self, uid, flags):
|
||||
"""Sets the specified message's flags to the given set."""
|
||||
raise NotImplementedException
|
||||
|
||||
def addmessageflags(self, uid, flags):
|
||||
"""Adds the specified flags to the message's flag set. If a given
|
||||
flag is already present, it will not be duplicated."""
|
||||
newflags = self.getmessageflags(uid)
|
||||
for flag in flags:
|
||||
if not flag in newflags:
|
||||
newflags.append(flag)
|
||||
newflags.sort()
|
||||
self.savemessageflags(uid, newflags)
|
||||
|
||||
def addmessagesflags(self, uidlist, flags):
|
||||
for uid in uidlist:
|
||||
self.addmessageflags(uid)
|
||||
|
||||
def deletemessageflags(self, uid, flags):
|
||||
"""Removes each flag given from the message's flag set. If a given
|
||||
flag is already removed, no action will be taken for that flag."""
|
||||
newflags = self.getmessageflags(uid)
|
||||
for flag in flags:
|
||||
if flag in newflags:
|
||||
newflags.remove(flag)
|
||||
newflags.sort()
|
||||
self.savemessageflags(uid, newflags)
|
||||
|
||||
def deletemessage(self, uid):
|
||||
raise NotImplementedException
|
||||
|
||||
def deletemessages(self, uidlist):
|
||||
for uid in uidlist:
|
||||
self.deletemessage(uid)
|
||||
|
||||
def syncmessagesto_neguid(self, dest, applyto):
|
||||
"""Pass 1 of folder synchronization.
|
||||
|
||||
Look for messages in self with a negative uid. These are messages in
|
||||
Maildirs that were not added by us. Try to add them to the dests,
|
||||
and once that succeeds, get the UID, add it to the others for real,
|
||||
add it to local for real, and delete the fake one."""
|
||||
|
||||
for uid in self.getmessagelist().keys():
|
||||
if uid >= 0:
|
||||
continue
|
||||
__main__.ui.copyingmessage(uid, self, applyto)
|
||||
successobject = None
|
||||
successuid = None
|
||||
message = self.getmessage(uid)
|
||||
flags = self.getmessageflags(uid)
|
||||
for tryappend in applyto:
|
||||
successuid = tryappend.savemessage(uid, message, flags)
|
||||
if successuid > 0:
|
||||
successobject = tryappend
|
||||
break
|
||||
# Did we succeed?
|
||||
if successobject != None:
|
||||
# Copy the message to the other remote servers.
|
||||
for appendserver in [x for x in applyto if x != successobject]:
|
||||
appendserver.savemessage(successuid, message, flags)
|
||||
# Copy it to its new name on the local server and delete
|
||||
# the one without a UID.
|
||||
self.savemessage(successuid, message, flags)
|
||||
self.deletemessage(uid)
|
||||
else:
|
||||
# Did not find any server to take this message. Ignore.
|
||||
pass
|
||||
|
||||
def copymessageto(self, uid, applyto):
|
||||
# Sometimes, it could be the case that if a sync takes awhile,
|
||||
# a message might be deleted from the maildir before it can be
|
||||
# synced to the status cache. This is only a problem with
|
||||
# self.getmessage(). So, don't call self.getmessage unless
|
||||
# really needed.
|
||||
__main__.ui.copyingmessage(uid, self, applyto)
|
||||
message = ''
|
||||
# If any of the destinations actually stores the message body,
|
||||
# load it up.
|
||||
for object in applyto:
|
||||
if object.storesmessages():
|
||||
message = self.getmessage(uid)
|
||||
break
|
||||
flags = self.getmessageflags(uid)
|
||||
for object in applyto:
|
||||
newuid = object.savemessage(uid, message, flags)
|
||||
if newuid > 0 and newuid != uid:
|
||||
# Change the local uid.
|
||||
self.savemessage(newuid, message, flags)
|
||||
self.deletemessage(uid)
|
||||
uid = newuid
|
||||
|
||||
|
||||
def syncmessagesto_copy(self, dest, applyto):
|
||||
"""Pass 2 of folder synchronization.
|
||||
|
||||
Look for messages present in self but not in dest. If any, add
|
||||
them to dest."""
|
||||
threads = []
|
||||
|
||||
for uid in self.getmessagelist().keys():
|
||||
if uid < 0: # Ignore messages that pass 1 missed.
|
||||
continue
|
||||
if not uid in dest.getmessagelist():
|
||||
if self.suggeststhreads():
|
||||
self.waitforthread()
|
||||
thread = InstanceLimitedThread(\
|
||||
self.getcopyinstancelimit(),
|
||||
target = self.copymessageto,
|
||||
name = "Copy message %d from %s" % (uid,
|
||||
self.getvisiblename()),
|
||||
args = (uid, applyto))
|
||||
thread.setDaemon(1)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
else:
|
||||
self.copymessageto(uid, applyto)
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
def syncmessagesto_delete(self, dest, applyto):
|
||||
"""Pass 3 of folder synchronization.
|
||||
|
||||
Look for message present in dest but not in self.
|
||||
If any, delete them."""
|
||||
deletelist = []
|
||||
for uid in dest.getmessagelist().keys():
|
||||
if uid < 0:
|
||||
continue
|
||||
if not uid in self.getmessagelist():
|
||||
deletelist.append(uid)
|
||||
if len(deletelist):
|
||||
__main__.ui.deletingmessages(deletelist, applyto)
|
||||
for object in applyto:
|
||||
object.deletemessages(deletelist)
|
||||
|
||||
def syncmessagesto_flags(self, dest, applyto):
|
||||
"""Pass 4 of folder synchronization.
|
||||
|
||||
Look for any flag matching issues -- set dest message to have the
|
||||
same flags that we have."""
|
||||
for uid in self.getmessagelist().keys():
|
||||
if uid < 0: # Ignore messages missed by pass 1
|
||||
continue
|
||||
selfflags = self.getmessageflags(uid)
|
||||
destflags = dest.getmessageflags(uid)
|
||||
|
||||
addflags = [x for x in selfflags if x not in destflags]
|
||||
if len(addflags):
|
||||
__main__.ui.addingflags(uid, addflags, applyto)
|
||||
for object in applyto:
|
||||
object.addmessageflags(uid, addflags)
|
||||
|
||||
delflags = [x for x in destflags if x not in selfflags]
|
||||
if len(delflags):
|
||||
__main__.ui.deletingflags(uid, delflags, applyto)
|
||||
for object in applyto:
|
||||
object.deletemessageflags(uid, delflags)
|
||||
|
||||
def syncmessagesto(self, dest, applyto = None):
|
||||
"""Syncs messages in this folder to the destination.
|
||||
If applyto is specified, it should be a list of folders (don't forget
|
||||
to include dest!) to which all write actions should be applied.
|
||||
It defaults to [dest] if not specified. It is important that
|
||||
the UID generator be listed first in applyto; that is, the other
|
||||
applyto ones should be the ones that "copy" the main action."""
|
||||
if applyto == None:
|
||||
applyto = [dest]
|
||||
|
||||
self.syncmessagesto_neguid(dest, applyto)
|
||||
self.syncmessagesto_copy(dest, applyto)
|
||||
self.syncmessagesto_delete(dest, applyto)
|
||||
|
||||
# Now, the message lists should be identical wrt the uids present.
|
||||
# (except for potential negative uids that couldn't be placed
|
||||
# anywhere)
|
||||
|
||||
self.syncmessagesto_flags(dest, applyto)
|
||||
|
||||
|
@ -1,203 +0,0 @@
|
||||
# IMAP folder 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Base import BaseFolder
|
||||
from offlineimap import imaputil, imaplib
|
||||
import rfc822
|
||||
from StringIO import StringIO
|
||||
from copy import copy
|
||||
|
||||
class IMAPFolder(BaseFolder):
|
||||
def __init__(self, imapserver, name, visiblename, accountname):
|
||||
self.name = imaputil.dequote(name)
|
||||
self.root = imapserver.root
|
||||
self.sep = imapserver.delim
|
||||
self.imapserver = imapserver
|
||||
self.messagelist = None
|
||||
self.visiblename = visiblename
|
||||
self.accountname = accountname
|
||||
|
||||
def suggeststhreads(self):
|
||||
return 1
|
||||
|
||||
def waitforthread(self):
|
||||
self.imapserver.connectionwait()
|
||||
|
||||
def getcopyinstancelimit(self):
|
||||
return 'MSGCOPY_' + self.accountname
|
||||
|
||||
def getvisiblename(self):
|
||||
return self.visiblename
|
||||
|
||||
def getuidvalidity(self):
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
try:
|
||||
# Primes untagged_responses
|
||||
imapobj.select(self.getfullname(), readonly = 1)
|
||||
return long(imapobj.untagged_responses['UIDVALIDITY'][0])
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
|
||||
def cachemessagelist(self):
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
self.messagelist = {}
|
||||
|
||||
try:
|
||||
# Primes untagged_responses
|
||||
imapobj.select(self.getfullname(), readonly = 1)
|
||||
maxmsgid = long(imapobj.untagged_responses['EXISTS'][0])
|
||||
if maxmsgid < 1:
|
||||
# No messages; return
|
||||
return
|
||||
|
||||
# Now, get the flags and UIDs for these.
|
||||
# We could conceivably get rid of maxmsgid and just say
|
||||
# '1:*' here.
|
||||
response = imapobj.fetch('1:%d' % maxmsgid, '(FLAGS UID)')[1]
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
for messagestr in response:
|
||||
# Discard the message number.
|
||||
messagestr = imaputil.imapsplit(messagestr)[1]
|
||||
options = imaputil.flags2hash(messagestr)
|
||||
uid = long(options['UID'])
|
||||
flags = imaputil.flagsimap2maildir(options['FLAGS'])
|
||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
||||
|
||||
def getmessagelist(self):
|
||||
return self.messagelist
|
||||
|
||||
def getmessage(self, uid):
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
try:
|
||||
imapobj.select(self.getfullname(), readonly = 1)
|
||||
return imapobj.uid('fetch', '%d' % uid, '(BODY.PEEK[])')[1][0][1].replace("\r\n", "\n")
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
|
||||
def getmessageflags(self, uid):
|
||||
return self.messagelist[uid]['flags']
|
||||
|
||||
def savemessage(self, uid, content, flags):
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
try:
|
||||
imapobj.select(self.getfullname()) # Needed for search
|
||||
|
||||
# This backend always assigns a new uid, so the uid arg is ignored.
|
||||
# In order to get the new uid, we need to save off the message ID.
|
||||
|
||||
message = rfc822.Message(StringIO(content))
|
||||
mid = imapobj._quote(message.getheader('Message-Id'))
|
||||
date = imaplib.Time2Internaldate(rfc822.parsedate(message.getheader('Date')))
|
||||
|
||||
if content.find("\r\n") == -1: # Convert line endings if not already
|
||||
content = content.replace("\n", "\r\n")
|
||||
|
||||
assert(imapobj.append(self.getfullname(),
|
||||
imaputil.flagsmaildir2imap(flags),
|
||||
date, content)[0] == 'OK')
|
||||
# Checkpoint. Let it write out the messages, etc.
|
||||
assert(imapobj.check()[0] == 'OK')
|
||||
# Now find the UID it got.
|
||||
matchinguids = imapobj.uid('search', None,
|
||||
'(HEADER Message-Id %s)' % mid)[1][0]
|
||||
matchinguids = matchinguids.split(' ')
|
||||
matchinguids.sort()
|
||||
uid = long(matchinguids[-1])
|
||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
||||
return uid
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
|
||||
def savemessageflags(self, uid, flags):
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
try:
|
||||
imapobj.select(self.getfullname())
|
||||
result = imapobj.uid('store', '%d' % uid, 'FLAGS',
|
||||
imaputil.flagsmaildir2imap(flags))
|
||||
assert result[0] == 'OK', 'Error with store: ' + r[1]
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
result = result[1][0]
|
||||
if not result:
|
||||
self.messagelist[uid]['flags'] = flags
|
||||
else:
|
||||
flags = imaputil.flags2hash(imaputil.imapsplit(result)[1])['FLAGS']
|
||||
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
|
||||
|
||||
def addmessageflags(self, uid, flags):
|
||||
self.addmessagesflags([uid], flags)
|
||||
|
||||
def addmessagesflags(self, uidlist, flags):
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
try:
|
||||
imapobj.select(self.getfullname())
|
||||
r = imapobj.uid('store',
|
||||
imaputil.listjoin(uidlist),
|
||||
'+FLAGS',
|
||||
imaputil.flagsmaildir2imap(flags))
|
||||
assert r[0] == 'OK', 'Error with store: ' + r[1]
|
||||
r = r[1]
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
# Some IMAP servers do not always return a result. Therefore,
|
||||
# only update the ones that it talks about, and manually fix
|
||||
# the others.
|
||||
needupdate = copy(uidlist)
|
||||
for result in r:
|
||||
if result == None:
|
||||
# Compensate for servers that don't return anything from
|
||||
# STORE.
|
||||
continue
|
||||
attributehash = imaputil.flags2hash(imaputil.imapsplit(result)[1])
|
||||
if not ('UID' in attributehash and 'FLAGS' in attributehash):
|
||||
# Compensate for servers that don't return a UID attribute.
|
||||
continue
|
||||
flags = attributehash['FLAGS']
|
||||
uid = long(attributehash['UID'])
|
||||
self.messagelist[uid]['flags'] = imaputil.flagsimap2maildir(flags)
|
||||
try:
|
||||
needupdate.remove(uid)
|
||||
except ValueError: # Let it slide if it's not in the list
|
||||
pass
|
||||
for uid in needupdate:
|
||||
for flag in flags:
|
||||
if not flag in self.messagelist[uid]['flags']:
|
||||
self.messagelist[uid]['flags'].append(flag)
|
||||
self.messagelist[uid]['flags'].sort()
|
||||
|
||||
def deletemessage(self, uid):
|
||||
self.deletemessages([uid])
|
||||
|
||||
def deletemessages(self, uidlist):
|
||||
# Weed out ones not in self.messagelist
|
||||
uidlist = [uid for uid in uidlist if uid in self.messagelist]
|
||||
if not len(uidlist):
|
||||
return
|
||||
|
||||
self.addmessagesflags(uidlist, ['T'])
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
try:
|
||||
imapobj.select(self.getfullname())
|
||||
assert(imapobj.expunge()[0] == 'OK')
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
for uid in uidlist:
|
||||
del self.messagelist[uid]
|
||||
|
||||
|
@ -1,102 +0,0 @@
|
||||
# Local status cache virtual folder
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Base import BaseFolder
|
||||
import os
|
||||
|
||||
magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
|
||||
|
||||
class LocalStatusFolder(BaseFolder):
|
||||
def __init__(self, root, name):
|
||||
self.name = name
|
||||
self.root = root
|
||||
self.sep = '.'
|
||||
self.filename = os.path.join(root, name)
|
||||
self.messagelist = None
|
||||
|
||||
def storesmessages(self):
|
||||
return 0
|
||||
|
||||
def isnewfolder(self):
|
||||
return not os.path.exists(self.filename)
|
||||
|
||||
def getname(self):
|
||||
return self.name
|
||||
|
||||
def getroot(self):
|
||||
return self.root
|
||||
|
||||
def getsep(self):
|
||||
return self.sep
|
||||
|
||||
def getfullname(self):
|
||||
return self.filename
|
||||
|
||||
def cachemessagelist(self):
|
||||
if self.isnewfolder():
|
||||
self.messagelist = {}
|
||||
return
|
||||
file = open(self.filename, "rt")
|
||||
self.messagelist = {}
|
||||
line = file.readline().strip()
|
||||
assert(line == magicline)
|
||||
for line in file.xreadlines():
|
||||
line = line.strip()
|
||||
uid, flags = line.split(':')
|
||||
uid = long(uid)
|
||||
flags = [x for x in flags]
|
||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
||||
file.close()
|
||||
|
||||
def save(self):
|
||||
file = open(self.filename + ".tmp", "wt")
|
||||
file.write(magicline + "\n")
|
||||
for msg in self.messagelist.values():
|
||||
flags = msg['flags']
|
||||
flags.sort()
|
||||
flags = ''.join(flags)
|
||||
file.write("%s:%s\n" % (msg['uid'], flags))
|
||||
file.close()
|
||||
os.rename(self.filename + ".tmp", self.filename)
|
||||
|
||||
def getmessagelist(self):
|
||||
return self.messagelist
|
||||
|
||||
def savemessage(self, uid, content, flags):
|
||||
if uid < 0:
|
||||
# We cannot assign a uid.
|
||||
return uid
|
||||
|
||||
if uid in self.messagelist: # already have it
|
||||
self.savemessageflags(uid, flags)
|
||||
return uid
|
||||
|
||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
||||
return uid
|
||||
|
||||
def getmessageflags(self, uid):
|
||||
return self.messagelist[uid]['flags']
|
||||
|
||||
def savemessageflags(self, uid, flags):
|
||||
self.messagelist[uid]['flags'] = flags
|
||||
|
||||
def deletemessage(self, uid):
|
||||
if not uid in self.messagelist:
|
||||
return
|
||||
del(self.messagelist[uid])
|
||||
|
@ -1,211 +0,0 @@
|
||||
# Maildir folder 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Base import BaseFolder
|
||||
from offlineimap import imaputil
|
||||
import os.path, os, re, time, socket, md5
|
||||
|
||||
timeseq = 0
|
||||
lasttime = long(0)
|
||||
|
||||
def gettimeseq():
|
||||
global lasttime, timeseq
|
||||
thistime = long(time.time())
|
||||
if thistime == lasttime:
|
||||
timeseq += 1
|
||||
return timeseq
|
||||
else:
|
||||
lasttime = long(time.time())
|
||||
timeseq = 0
|
||||
return timeseq
|
||||
|
||||
class MaildirFolder(BaseFolder):
|
||||
def __init__(self, root, name):
|
||||
self.name = name
|
||||
self.root = root
|
||||
self.sep = '.'
|
||||
self.uidfilename = os.path.join(self.getfullname(), "offlineimap.uidvalidity")
|
||||
self.messagelist = None
|
||||
|
||||
def getfullname(self):
|
||||
return os.path.join(self.getroot(), self.getname())
|
||||
|
||||
def getuidvalidity(self):
|
||||
if not os.path.exists(self.uidfilename):
|
||||
return None
|
||||
file = open(self.uidfilename, "rt")
|
||||
retval = long(file.readline().strip())
|
||||
file.close()
|
||||
return retval
|
||||
|
||||
def saveuidvalidity(self, newval):
|
||||
file = open(self.uidfilename, "wt")
|
||||
file.write("%d\n" % newval)
|
||||
file.close()
|
||||
|
||||
def isuidvalidityok(self, remotefolder):
|
||||
myval = self.getuidvalidity()
|
||||
if myval != None:
|
||||
return myval == remotefolder.getuidvalidity()
|
||||
else:
|
||||
self.saveuidvalidity(remotefolder.getuidvalidity())
|
||||
return 1
|
||||
|
||||
def _scanfolder(self):
|
||||
"""Cache the message list. Maildir flags are:
|
||||
R (replied)
|
||||
S (seen)
|
||||
T (trashed)
|
||||
D (draft)
|
||||
F (flagged)
|
||||
and must occur in ASCII order."""
|
||||
retval = {}
|
||||
files = []
|
||||
nouidcounter = -1 # Messages without UIDs get
|
||||
# negative UID numbers.
|
||||
for dirannex in ['new', 'cur']:
|
||||
fulldirname = os.path.join(self.getfullname(), dirannex)
|
||||
files.extend([os.path.join(fulldirname, filename) for
|
||||
filename in os.listdir(fulldirname)])
|
||||
for file in files:
|
||||
messagename = os.path.basename(file)
|
||||
foldermatch = re.search(',FMD5=([0-9a-f]{32})', messagename)
|
||||
if (not foldermatch) or \
|
||||
md5.new(self.getvisiblename()).hexdigest() \
|
||||
!= foldermatch.group(1):
|
||||
# If there is no folder MD5 specified, or if it mismatches,
|
||||
# assume it is a foreign (new) message and generate a
|
||||
# negative uid for it
|
||||
uid = nouidcounter
|
||||
nouidcounter -= 1
|
||||
else: # It comes from our folder.
|
||||
uidmatch = re.search(',U=(\d+)', messagename)
|
||||
uid = None
|
||||
if not uidmatch:
|
||||
uid = nouidcounter
|
||||
nouidcounter -= 1
|
||||
else:
|
||||
uid = long(uidmatch.group(1))
|
||||
flagmatch = re.search(':.*2,([A-Z]+)', messagename)
|
||||
flags = []
|
||||
if flagmatch:
|
||||
flags = [x for x in flagmatch.group(1)]
|
||||
flags.sort()
|
||||
if 'T' in flags:
|
||||
# Message is marked for deletion; just delete it now.
|
||||
# Otherwise, the T flag will be propogated to the IMAP
|
||||
# server, and then expunged there, and then deleted here.
|
||||
# Might as well just delete it now, to help make things
|
||||
# more robust.
|
||||
os.unlink(file)
|
||||
else:
|
||||
retval[uid] = {'uid': uid,
|
||||
'flags': flags,
|
||||
'filename': file}
|
||||
return retval
|
||||
|
||||
def cachemessagelist(self):
|
||||
self.messagelist = self._scanfolder()
|
||||
|
||||
def getmessagelist(self):
|
||||
return self.messagelist
|
||||
|
||||
def getmessage(self, uid):
|
||||
filename = self.getmessagelist()[uid]['filename']
|
||||
file = open(filename, 'rt')
|
||||
retval = file.read()
|
||||
file.close()
|
||||
return retval
|
||||
|
||||
def savemessage(self, uid, content, flags):
|
||||
if uid < 0:
|
||||
# We cannot assign a new uid.
|
||||
return uid
|
||||
if uid in self.getmessagelist():
|
||||
# We already have it.
|
||||
self.savemessageflags(uid, flags)
|
||||
return uid
|
||||
newdir = os.path.join(self.getfullname(), 'new')
|
||||
tmpdir = os.path.join(self.getfullname(), 'tmp')
|
||||
messagename = None
|
||||
attempts = 0
|
||||
while 1:
|
||||
if attempts > 15:
|
||||
raise IOError, "Couldn't write to file %s" % messagename
|
||||
messagename = '%d_%d.%d.%s,U=%d,FMD5=%s' % \
|
||||
(long(time.time()),
|
||||
gettimeseq(),
|
||||
os.getpid(),
|
||||
socket.gethostname(),
|
||||
uid,
|
||||
md5.new(self.getvisiblename()).hexdigest())
|
||||
if os.path.exists(os.path.join(tmpdir, messagename)):
|
||||
time.sleep(2)
|
||||
attempts += 1
|
||||
else:
|
||||
break
|
||||
file = open(os.path.join(tmpdir, messagename), "wt")
|
||||
file.write(content)
|
||||
file.close()
|
||||
os.link(os.path.join(tmpdir, messagename),
|
||||
os.path.join(newdir, messagename))
|
||||
os.unlink(os.path.join(tmpdir, messagename))
|
||||
self.messagelist[uid] = {'uid': uid, 'flags': [],
|
||||
'filename': os.path.join(newdir, messagename)}
|
||||
self.savemessageflags(uid, flags)
|
||||
return uid
|
||||
|
||||
def getmessageflags(self, uid):
|
||||
return self.getmessagelist()[uid]['flags']
|
||||
|
||||
def savemessageflags(self, uid, flags):
|
||||
oldfilename = self.getmessagelist()[uid]['filename']
|
||||
newpath, newname = os.path.split(oldfilename)
|
||||
infostr = ':'
|
||||
infomatch = re.search('(:.*)$', newname)
|
||||
if infomatch: # If the info string is present..
|
||||
infostr = infomatch.group(1)
|
||||
newname = newname.split(':')[0] # Strip off the info string.
|
||||
infostr = re.sub('2,[A-Z]*', '', infostr)
|
||||
flags.sort()
|
||||
infostr += '2,' + ''.join(flags)
|
||||
newname += infostr
|
||||
|
||||
newfilename = os.path.join(newpath, newname)
|
||||
if (newfilename != oldfilename):
|
||||
os.rename(oldfilename, newfilename)
|
||||
self.getmessagelist()[uid]['flags'] = flags
|
||||
self.getmessagelist()[uid]['filename'] = newfilename
|
||||
|
||||
def getmessageflags(self, uid):
|
||||
return self.getmessagelist()[uid]['flags']
|
||||
|
||||
def deletemessage(self, uid):
|
||||
if not uid in self.messagelist:
|
||||
return
|
||||
filename = self.getmessagelist()[uid]['filename']
|
||||
try:
|
||||
os.unlink(filename)
|
||||
except IOError:
|
||||
# Can't find the file -- maybe already deleted?
|
||||
newmsglist = self._scanfolder()
|
||||
if uid in newmsglist: # Nope, try new filename.
|
||||
os.unlink(newmsglist[uid]['filename'])
|
||||
# Yep -- return.
|
||||
del(self.messagelist[uid])
|
||||
|
@ -1,2 +0,0 @@
|
||||
import Base, IMAP, Maildir, LocalStatus
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,234 +0,0 @@
|
||||
# IMAP server 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from offlineimap import imaplib, imaputil, threadutil
|
||||
from threading import *
|
||||
import thread
|
||||
|
||||
class UsefulIMAPMixIn:
|
||||
def getstate(self):
|
||||
return self.state
|
||||
def getselectedfolder(self):
|
||||
if self.getstate() == 'SELECTED':
|
||||
return self.selectedfolder
|
||||
return None
|
||||
|
||||
def select(self, mailbox='INBOX', readonly=None):
|
||||
if self.getselectedfolder() == mailbox:
|
||||
self.is_readonly = readonly
|
||||
# No change; return.
|
||||
return
|
||||
result = self.__class__.__bases__[1].select(self, mailbox, readonly)
|
||||
if result[0] != 'OK':
|
||||
raise ValueError, "Error from select: %s" % str(result)
|
||||
if self.getstate() == 'SELECTED':
|
||||
self.selectedfolder = mailbox
|
||||
else:
|
||||
self.selectedfolder = None
|
||||
|
||||
class UsefulIMAP4(UsefulIMAPMixIn, imaplib.IMAP4): pass
|
||||
class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL): pass
|
||||
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass
|
||||
|
||||
class IMAPServer:
|
||||
def __init__(self, username = None, password = None, hostname = None,
|
||||
port = None, ssl = 1, maxconnections = 1, tunnel = None,
|
||||
reference = '""'):
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.hostname = hostname
|
||||
self.tunnel = tunnel
|
||||
self.port = port
|
||||
self.usessl = ssl
|
||||
self.delim = None
|
||||
self.root = None
|
||||
if port == None:
|
||||
if ssl:
|
||||
self.port = 993
|
||||
else:
|
||||
self.port = 143
|
||||
self.maxconnections = maxconnections
|
||||
self.availableconnections = []
|
||||
self.assignedconnections = []
|
||||
self.lastowner = {}
|
||||
self.semaphore = BoundedSemaphore(self.maxconnections)
|
||||
self.connectionlock = Lock()
|
||||
self.reference = reference
|
||||
|
||||
def getdelim(self):
|
||||
"""Returns this server's folder delimiter. Can only be called
|
||||
after one or more calls to acquireconnection."""
|
||||
return self.delim
|
||||
|
||||
def getroot(self):
|
||||
"""Returns this server's folder root. Can only be called after one
|
||||
or more calls to acquireconnection."""
|
||||
return self.root
|
||||
|
||||
|
||||
def releaseconnection(self, connection):
|
||||
self.connectionlock.acquire()
|
||||
self.assignedconnections.remove(connection)
|
||||
self.availableconnections.append(connection)
|
||||
self.connectionlock.release()
|
||||
self.semaphore.release()
|
||||
|
||||
def acquireconnection(self):
|
||||
"""Fetches a connection from the pool, making sure to create a new one
|
||||
if needed, to obey the maximum connection limits, etc.
|
||||
Opens a connection to the server and returns an appropriate
|
||||
object."""
|
||||
|
||||
self.semaphore.acquire()
|
||||
self.connectionlock.acquire()
|
||||
imapobj = None
|
||||
|
||||
if len(self.availableconnections): # One is available.
|
||||
# Try to find one that previously belonged to this thread
|
||||
# as an optimization. Start from the back since that's where
|
||||
# they're popped on.
|
||||
threadid = thread.get_ident()
|
||||
imapobj = None
|
||||
for i in range(len(self.availableconnections) - 1, -1, -1):
|
||||
tryobj = self.availableconnections[i]
|
||||
if self.lastowner[tryobj] == threadid:
|
||||
imapobj = tryobj
|
||||
del(self.availableconnections[i])
|
||||
break
|
||||
if not imapobj:
|
||||
imapobj = self.availableconnections[0]
|
||||
del(self.availableconnections[0])
|
||||
self.assignedconnections.append(imapobj)
|
||||
self.lastowner[imapobj] = thread.get_ident()
|
||||
self.connectionlock.release()
|
||||
return imapobj
|
||||
|
||||
self.connectionlock.release() # Release until need to modify data
|
||||
|
||||
# Generate a new connection.
|
||||
if self.tunnel:
|
||||
imapobj = UsefulIMAP4_Tunnel(self.tunnel)
|
||||
elif self.usessl:
|
||||
imapobj = UsefulIMAP4_SSL(self.hostname, self.port)
|
||||
else:
|
||||
imapobj = UsefulIMAP4(self.hostname, self.port)
|
||||
|
||||
if not self.tunnel:
|
||||
imapobj.login(self.username, self.password)
|
||||
|
||||
if self.delim == None:
|
||||
self.delim, self.root = \
|
||||
imaputil.imapsplit(imapobj.list(self.reference, '""')[1][0])[1:]
|
||||
self.delim = imaputil.dequote(self.delim)
|
||||
self.root = imaputil.dequote(self.root)
|
||||
|
||||
self.connectionlock.acquire()
|
||||
self.assignedconnections.append(imapobj)
|
||||
self.lastowner[imapobj] = thread.get_ident()
|
||||
self.connectionlock.release()
|
||||
return imapobj
|
||||
|
||||
def connectionwait(self):
|
||||
"""Waits until there is a connection available. Note that between
|
||||
the time that a connection becomes available and the time it is
|
||||
requested, another thread may have grabbed it. This function is
|
||||
mainly present as a way to avoid spawning thousands of threads
|
||||
to copy messages, then have them all wait for 3 available connections.
|
||||
It's OK if we have maxconnections + 1 or 2 threads, which is what
|
||||
this will help us do."""
|
||||
threadutil.semaphorewait(self.semaphore)
|
||||
|
||||
def close(self):
|
||||
# Make sure I own all the semaphores. Let the threads finish
|
||||
# their stuff. This is a blocking method.
|
||||
self.connectionlock.acquire()
|
||||
threadutil.semaphorereset(self.semaphore, self.maxconnections)
|
||||
for imapobj in self.assignedconnections + self.availableconnections:
|
||||
imapobj.logout()
|
||||
self.assignedconnections = []
|
||||
self.availableconnections = []
|
||||
self.lastowner = {}
|
||||
self.connectionlock.release()
|
||||
|
||||
def keepalive(self, timeout, event):
|
||||
"""Sends a NOOP to each connection recorded. It will wait a maximum
|
||||
of timeout seconds between doing this, and will continue to do so
|
||||
until the Event object as passed is true. This method is expected
|
||||
to be invoked in a separate thread, which should be join()'d after
|
||||
the event is set."""
|
||||
while 1:
|
||||
event.wait(timeout)
|
||||
if event.isSet():
|
||||
return
|
||||
self.connectionlock.acquire()
|
||||
numconnections = len(self.assignedconnections) + \
|
||||
len(self.availableconnections)
|
||||
self.connectionlock.release()
|
||||
threads = []
|
||||
imapobjs = []
|
||||
|
||||
for i in range(numconnections):
|
||||
imapobj = self.acquireconnection()
|
||||
imapobjs.append(imapobj)
|
||||
thr = threadutil.ExitNotifyThread(target = imapobj.noop)
|
||||
thr.setDaemon(1)
|
||||
thr.start()
|
||||
threads.append(thr)
|
||||
|
||||
for thr in threads:
|
||||
# Make sure all the commands have completed.
|
||||
thr.join()
|
||||
|
||||
for imapobj in imapobjs:
|
||||
self.releaseconnection(imapobj)
|
||||
|
||||
class ConfigedIMAPServer(IMAPServer):
|
||||
"""This class is designed for easier initialization given a ConfigParser
|
||||
object and an account name. The passwordhash is used if
|
||||
passwords for certain accounts are known. If the password for this
|
||||
account is listed, it will be obtained from there."""
|
||||
def __init__(self, config, accountname, passwordhash = {}):
|
||||
"""Initialize the object. If the account is not a tunnel,
|
||||
the password is required."""
|
||||
host = config.get(accountname, "remotehost")
|
||||
user = config.get(accountname, "remoteuser")
|
||||
port = None
|
||||
if config.has_option(accountname, "remoteport"):
|
||||
port = config.getint(accountname, "remoteport")
|
||||
ssl = config.getboolean(accountname, "ssl")
|
||||
usetunnel = config.has_option(accountname, "preauthtunnel")
|
||||
reference = '""'
|
||||
if config.has_option(accountname, "reference"):
|
||||
reference = config.get(accountname, "reference")
|
||||
server = None
|
||||
password = None
|
||||
if accountname in passwordhash:
|
||||
password = passwordhash[accountname]
|
||||
|
||||
# Connect to the remote server.
|
||||
if usetunnel:
|
||||
IMAPServer.__init__(self,
|
||||
tunnel = config.get(accountname, "preauthtunnel"),
|
||||
reference = reference,
|
||||
maxconnections = config.getint(accountname, "maxconnections"))
|
||||
else:
|
||||
if not password:
|
||||
password = config.get(accountname, 'remotepass')
|
||||
IMAPServer.__init__(self, user, password, host, port, ssl,
|
||||
config.getint(accountname, "maxconnections"),
|
||||
reference = reference)
|
@ -1,139 +0,0 @@
|
||||
# IMAP utility module
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
import re
|
||||
|
||||
def dequote(string):
|
||||
"""Takes a string which may or may not be quoted and returns it, unquoted.
|
||||
This function does NOT consider parenthised lists to be quoted.
|
||||
"""
|
||||
|
||||
if not (string[0] == '"' and string[-1] == '"'):
|
||||
return string
|
||||
string = string[1:-1] # Strip off quotes.
|
||||
string = string.replace('\\"', '"')
|
||||
string = string.replace('\\\\', '\\')
|
||||
return string
|
||||
|
||||
def flagsplit(string):
|
||||
if string[0] != '(' or string[-1] != ')':
|
||||
raise ValueError, "Passed string '%s' is not a flag list" % string
|
||||
return imapsplit(string[1:-1])
|
||||
|
||||
def options2hash(list):
|
||||
retval = {}
|
||||
counter = 0
|
||||
while (counter < len(list)):
|
||||
retval[list[counter]] = list[counter + 1]
|
||||
counter += 2
|
||||
return retval
|
||||
|
||||
def flags2hash(string):
|
||||
return options2hash(flagsplit(string))
|
||||
|
||||
def imapsplit(string):
|
||||
"""Takes a string from an IMAP conversation and returns a list containing
|
||||
its components. One example string is:
|
||||
|
||||
(\\HasNoChildren) "." "INBOX.Sent"
|
||||
|
||||
The result from parsing this will be:
|
||||
|
||||
['(\\HasNoChildren)', '"."', '"INBOX.Sent"']"""
|
||||
|
||||
workstr = string
|
||||
retval = []
|
||||
while len(workstr):
|
||||
if re.search('^\s', workstr):
|
||||
workstr = re.search('^\s(.*)$', workstr).group(1)
|
||||
elif workstr[0] == '(':
|
||||
parenlist = re.search('^(\(.*\))', workstr).group(1)
|
||||
workstr = workstr[len(parenlist):]
|
||||
retval.append(parenlist)
|
||||
elif workstr[0] == '"':
|
||||
quotelist = re.search('^("(?:[^"]|\\\\")*")', workstr).group(1)
|
||||
workstr = workstr[len(quotelist):]
|
||||
retval.append(quotelist)
|
||||
else:
|
||||
unq = re.search('^(\S+)', workstr).group(1)
|
||||
workstr = workstr[len(unq):]
|
||||
retval.append(unq)
|
||||
return retval
|
||||
|
||||
def flagsimap2maildir(string):
|
||||
flagmap = {'\\seen': 'S',
|
||||
'\\answered': 'R',
|
||||
'\\flagged': 'F',
|
||||
'\\deleted': 'T',
|
||||
'\\draft': 'D'}
|
||||
retval = []
|
||||
imapflaglist = [x.lower() for x in flagsplit(string)]
|
||||
for imapflag in imapflaglist:
|
||||
if flagmap.has_key(imapflag):
|
||||
retval.append(flagmap[imapflag])
|
||||
retval.sort()
|
||||
return retval
|
||||
|
||||
def flagsmaildir2imap(list):
|
||||
flagmap = {'S': '\\Seen',
|
||||
'R': '\\Answered',
|
||||
'F': '\\Flagged',
|
||||
'T': '\\Deleted',
|
||||
'D': '\\Draft'}
|
||||
retval = []
|
||||
for mdflag in list:
|
||||
if flagmap.has_key(mdflag):
|
||||
retval.append(flagmap[mdflag])
|
||||
retval.sort()
|
||||
return '(' + ' '.join(retval) + ')'
|
||||
|
||||
def listjoin(list):
|
||||
start = None
|
||||
end = None
|
||||
retval = []
|
||||
|
||||
def getlist(start, end):
|
||||
if start == end:
|
||||
return(str(start))
|
||||
else:
|
||||
return(str(start) + ":" + str(end))
|
||||
|
||||
|
||||
for item in list:
|
||||
if start == None:
|
||||
# First item.
|
||||
start = item
|
||||
end = item
|
||||
elif item == end + 1:
|
||||
# An addition to the list.
|
||||
end = item
|
||||
else:
|
||||
# Here on: starting a new list.
|
||||
retval.append(getlist(start, end))
|
||||
start = item
|
||||
end = item
|
||||
|
||||
if start != None:
|
||||
retval.append(getlist(start, end))
|
||||
|
||||
return ",".join(retval)
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,34 +0,0 @@
|
||||
# Mailbox name generator
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
import os.path
|
||||
|
||||
def genmbnames(config, boxlist):
|
||||
"""Takes a configparser object and a boxlist, which is a list of hashes
|
||||
containing 'accountname' and 'foldername' keys."""
|
||||
if not config.getboolean("mbnames", "enabled"):
|
||||
return
|
||||
file = open(os.path.expanduser(config.get("mbnames", "filename")), "wt")
|
||||
file.write(eval(config.get("mbnames", "header")))
|
||||
itemlist = [eval(config.get("mbnames", "peritem", raw=1)) % item for item in boxlist]
|
||||
file.write(eval(config.get("mbnames", "sep")).join(itemlist))
|
||||
file.write(eval(config.get("mbnames", "footer")))
|
||||
file.close()
|
||||
|
||||
|
||||
|
@ -1,69 +0,0 @@
|
||||
# Base 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
class BaseRepository:
|
||||
def getfolders(self):
|
||||
"""Returns a list of ALL folders on this server."""
|
||||
return []
|
||||
|
||||
def getsep(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def makefolder(self, foldername):
|
||||
raise NotImplementedError
|
||||
|
||||
def deletefolder(self, foldername):
|
||||
raise NotImplementedError
|
||||
|
||||
def getfolder(self, foldername):
|
||||
raise NotImplementedError
|
||||
|
||||
def syncfoldersto(self, dest):
|
||||
"""Syncs the folders in this repository to those in dest.
|
||||
It does NOT sync the contents of those folders."""
|
||||
src = self
|
||||
srcfolders = src.getfolders()
|
||||
destfolders = dest.getfolders()
|
||||
|
||||
# Create hashes with the names, but convert the source folders
|
||||
# to the dest folder's sep.
|
||||
|
||||
srchash = {}
|
||||
for folder in srcfolders:
|
||||
srchash[folder.getvisiblename().replace(src.getsep(), dest.getsep())] = \
|
||||
folder
|
||||
desthash = {}
|
||||
for folder in destfolders:
|
||||
desthash[folder.getvisiblename()] = folder
|
||||
|
||||
#
|
||||
# Find new folders.
|
||||
#
|
||||
|
||||
for key in srchash.keys():
|
||||
if not key in desthash:
|
||||
dest.makefolder(key)
|
||||
|
||||
#
|
||||
# Find deleted folders.
|
||||
#
|
||||
|
||||
for key in desthash.keys():
|
||||
if not key in srchash:
|
||||
dest.deletefolder(key)
|
||||
|
@ -1,76 +0,0 @@
|
||||
# IMAP 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Base import BaseRepository
|
||||
from offlineimap import folder, imaputil
|
||||
import re
|
||||
from threading import *
|
||||
|
||||
class IMAPRepository(BaseRepository):
|
||||
def __init__(self, config, accountname, imapserver):
|
||||
"""Initialize an IMAPRepository object. Takes an IMAPServer
|
||||
object."""
|
||||
self.imapserver = imapserver
|
||||
self.config = config
|
||||
self.accountname = accountname
|
||||
self.folders = None
|
||||
self.nametrans = lambda foldername: foldername
|
||||
self.folderfilter = lambda foldername: 1
|
||||
self.folderincludes = []
|
||||
if config.has_option(accountname, 'nametrans'):
|
||||
self.nametrans = eval(config.get(accountname, 'nametrans'))
|
||||
if config.has_option(accountname, 'folderfilter'):
|
||||
self.folderfilter = eval(config.get(accountname, 'folderfilter'))
|
||||
if config.has_option(accountname, 'folderincludes'):
|
||||
self.folderincludes = eval(config.get(accountname, 'folderincludes'))
|
||||
|
||||
def getsep(self):
|
||||
return self.imapserver.delim
|
||||
|
||||
def getfolder(self, foldername):
|
||||
return folder.IMAP.IMAPFolder(self.imapserver, foldername,
|
||||
self.nametrans(foldername),
|
||||
accountname)
|
||||
|
||||
def getfolders(self):
|
||||
if self.folders != None:
|
||||
return self.folders
|
||||
retval = []
|
||||
imapobj = self.imapserver.acquireconnection()
|
||||
try:
|
||||
listresult = imapobj.list(directory = self.imapserver.reference)[1]
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
for string in listresult:
|
||||
flags, delim, name = imaputil.imapsplit(string)
|
||||
flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
|
||||
if '\\noselect' in flaglist:
|
||||
continue
|
||||
foldername = imaputil.dequote(name)
|
||||
if not self.folderfilter(foldername):
|
||||
continue
|
||||
retval.append(folder.IMAP.IMAPFolder(self.imapserver, foldername,
|
||||
self.nametrans(foldername),
|
||||
self.accountname))
|
||||
for foldername in self.folderincludes:
|
||||
retval.append(folder.IMAP.IMAPFolder(self.imapserver, foldername,
|
||||
self.nametrans(foldername),
|
||||
self.accountname))
|
||||
retval.sort(lambda x, y: cmp(x.getvisiblename(), y.getvisiblename()))
|
||||
self.folders = retval
|
||||
return retval
|
@ -1,54 +0,0 @@
|
||||
# Local status cache 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Base import BaseRepository
|
||||
from offlineimap import folder
|
||||
import os
|
||||
|
||||
class LocalStatusRepository(BaseRepository):
|
||||
def __init__(self, directory):
|
||||
self.directory = directory
|
||||
self.folders = None
|
||||
|
||||
def getsep(self):
|
||||
return '.'
|
||||
|
||||
def getfolderfilename(self, foldername):
|
||||
return os.path.join(self.directory, foldername)
|
||||
|
||||
def makefolder(self, foldername):
|
||||
# "touch" the file.
|
||||
file = open(self.getfolderfilename(foldername), "ab")
|
||||
file.close()
|
||||
# Invalidate the cache.
|
||||
self.folders = None
|
||||
|
||||
def getfolders(self):
|
||||
retval = []
|
||||
for folder in os.listdir(self.directory):
|
||||
retval.append(folder.LocalStatus.LocalStatusFolder(self.directory,
|
||||
folder))
|
||||
return retval
|
||||
|
||||
def getfolder(self, foldername):
|
||||
return folder.LocalStatus.LocalStatusFolder(self.directory, foldername)
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,67 +0,0 @@
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Base import BaseRepository
|
||||
from offlineimap import folder, imaputil
|
||||
from mailbox import Maildir
|
||||
import os
|
||||
|
||||
class MaildirRepository(BaseRepository):
|
||||
def __init__(self, root):
|
||||
"""Initialize a MaildirRepository object. Takes a path name
|
||||
to the directory holding all the Maildir directories."""
|
||||
|
||||
self.root = root
|
||||
self.folders = None
|
||||
|
||||
def getsep(self):
|
||||
return '.'
|
||||
|
||||
|
||||
|
||||
def makefolder(self, foldername):
|
||||
folderdir = os.path.join(self.root, foldername)
|
||||
os.mkdir(folderdir, 0700)
|
||||
for subdir in ['cur', 'new', 'tmp']:
|
||||
os.mkdir(os.path.join(folderdir, subdir), 0700)
|
||||
# Invalidate the cache
|
||||
self.folders = None
|
||||
|
||||
def deletefolder(self, foldername):
|
||||
print "NOT YET IMPLEMENTED: DELETE FOLDER %s" % foldername
|
||||
|
||||
def getfolder(self, foldername):
|
||||
return folder.Maildir.MaildirFolder(self.root, foldername)
|
||||
|
||||
def getfolders(self):
|
||||
if self.folders != None:
|
||||
return self.folders
|
||||
|
||||
retval = []
|
||||
for dirname in os.listdir(self.root):
|
||||
fullname = os.path.join(self.root, dirname)
|
||||
if not os.path.isdir(fullname):
|
||||
continue
|
||||
if not (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'))):
|
||||
continue
|
||||
retval.append(folder.Maildir.MaildirFolder(self.root, dirname))
|
||||
self.folders = retval
|
||||
return retval
|
||||
|
@ -1 +0,0 @@
|
||||
import IMAP, Base, Maildir, LocalStatus
|
@ -1,165 +0,0 @@
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
# Thread support module
|
||||
# <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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from threading import *
|
||||
from StringIO import StringIO
|
||||
import sys, traceback, thread
|
||||
|
||||
######################################################################
|
||||
# General utilities
|
||||
######################################################################
|
||||
|
||||
def semaphorereset(semaphore, originalstate):
|
||||
"""Wait until the semaphore gets back to its original state -- all acquired
|
||||
resources released."""
|
||||
for i in range(originalstate):
|
||||
semaphore.acquire()
|
||||
# Now release these.
|
||||
for i in range(originalstate):
|
||||
semaphore.release()
|
||||
|
||||
def semaphorewait(semaphore):
|
||||
semaphore.acquire()
|
||||
semaphore.release()
|
||||
|
||||
def threadsreset(threadlist):
|
||||
for thr in threadlist:
|
||||
thr.join()
|
||||
|
||||
######################################################################
|
||||
# Exit-notify threads
|
||||
######################################################################
|
||||
|
||||
exitcondition = Condition(Lock())
|
||||
exitthreads = []
|
||||
inited = 0
|
||||
|
||||
def initexitnotify():
|
||||
"""Initialize the exit notify system. This MUST be called from the
|
||||
SAME THREAD that will call monitorloop BEFORE it calls monitorloop.
|
||||
This SHOULD be called before the main thread starts any other
|
||||
ExitNotifyThreads, or else it may miss the ability to catch the exit
|
||||
status from them!"""
|
||||
pass
|
||||
|
||||
def exitnotifymonitorloop(callback):
|
||||
"""Enter an infinite "monitoring" loop. The argument, callback,
|
||||
defines the function to call when an ExitNotifyThread has terminated.
|
||||
That function is called with a single argument -- the ExitNotifyThread
|
||||
that has terminated. The monitor will not continue to monitor for
|
||||
other threads until the function returns, so if it intends to perform
|
||||
long calculations, it should start a new thread itself -- but NOT
|
||||
an ExitNotifyThread, or else an infinite loop may result. Furthermore,
|
||||
the monitor will hold the lock all the while the other thread is waiting.
|
||||
"""
|
||||
global exitcondition, exitthreads
|
||||
while 1: # Loop forever.
|
||||
exitcondition.acquire()
|
||||
while not len(exitthreads):
|
||||
exitcondition.wait(1)
|
||||
|
||||
while len(exitthreads):
|
||||
callback(exitthreads.pop())
|
||||
exitcondition.release()
|
||||
|
||||
class ExitNotifyThread(Thread):
|
||||
"""This class is designed to alert a "monitor" to the fact that a thread has
|
||||
exited and to provide for the ability for it to find out why."""
|
||||
def run(self):
|
||||
global exitcondition, exitthreads
|
||||
self.threadid = thread.get_ident()
|
||||
try:
|
||||
Thread.run(self)
|
||||
except:
|
||||
self.setExitCause('EXCEPTION')
|
||||
self.setExitException(sys.exc_info()[1])
|
||||
sbuf = StringIO()
|
||||
traceback.print_exc(file = sbuf)
|
||||
self.setExitStackTrace(sbuf.getvalue())
|
||||
else:
|
||||
self.setExitCause('NORMAL')
|
||||
if not hasattr(self, 'exitmessage'):
|
||||
self.setExitMessage(None)
|
||||
exitcondition.acquire()
|
||||
exitthreads.append(self)
|
||||
exitcondition.notify()
|
||||
exitcondition.release()
|
||||
|
||||
def setExitCause(self, cause):
|
||||
self.exitcause = cause
|
||||
def getExitCause(self):
|
||||
"""Returns the cause of the exit, one of:
|
||||
'EXCEPTION' -- the thread aborted because of an exception
|
||||
'NORMAL' -- normal termination."""
|
||||
return self.exitcause
|
||||
def setExitException(self, exc):
|
||||
self.exitexception = exc
|
||||
def getExitException(self):
|
||||
"""If getExitCause() is 'EXCEPTION', holds the value from
|
||||
sys.exc_info()[1] for this exception."""
|
||||
return self.exitexception
|
||||
def setExitStackTrace(self, st):
|
||||
self.exitstacktrace = st
|
||||
def getExitStackTrace(self):
|
||||
"""If getExitCause() is 'EXCEPTION', returns a string representing
|
||||
the stack trace for this exception."""
|
||||
return self.exitstacktrace
|
||||
def setExitMessage(self, msg):
|
||||
"""Sets the exit message to be fetched by a subsequent call to
|
||||
getExitMessage. This message may be any object or type except
|
||||
None."""
|
||||
self.exitmessage = msg
|
||||
def getExitMessage(self):
|
||||
"""For any exit cause, returns the message previously set by
|
||||
a call to setExitMessage(), or None if there was no such message
|
||||
set."""
|
||||
return self.exitmessage
|
||||
|
||||
|
||||
######################################################################
|
||||
# Instance-limited threads
|
||||
######################################################################
|
||||
|
||||
instancelimitedsems = {}
|
||||
instancelimitedlock = Lock()
|
||||
|
||||
def initInstanceLimit(instancename, instancemax):
|
||||
"""Initialize the instance-limited thread implementation to permit
|
||||
up to intancemax threads with the given instancename."""
|
||||
instancelimitedlock.acquire()
|
||||
if not instancelimitedsems.has_key(instancename):
|
||||
instancelimitedsems[instancename] = BoundedSemaphore(instancemax)
|
||||
instancelimitedlock.release()
|
||||
|
||||
class InstanceLimitedThread(ExitNotifyThread):
|
||||
def __init__(self, instancename, *args, **kwargs):
|
||||
self.instancename = instancename
|
||||
|
||||
apply(ExitNotifyThread.__init__, (self,) + args, kwargs)
|
||||
|
||||
def start(self):
|
||||
instancelimitedsems[self.instancename].acquire()
|
||||
ExitNotifyThread.start(self)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
ExitNotifyThread.run(self)
|
||||
finally:
|
||||
instancelimitedsems[self.instancename].release()
|
||||
|
||||
|
@ -1,82 +0,0 @@
|
||||
# TTY UI
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from UIBase import UIBase
|
||||
from getpass import getpass
|
||||
import select, sys
|
||||
from threading import *
|
||||
|
||||
class TTYUI(UIBase):
|
||||
def __init__(self, verbose = 0):
|
||||
self.verbose = 0
|
||||
self.iswaiting = 0
|
||||
|
||||
def _msg(s, msg):
|
||||
if (currentThread().getName() == 'MainThread'):
|
||||
print msg
|
||||
else:
|
||||
print "%s:\n %s" % (currentThread().getName(), msg)
|
||||
sys.stdout.flush()
|
||||
|
||||
def getpass(s, accountname, config):
|
||||
return getpass("%s: Enter password for %s on %s: " %
|
||||
(accountname, config.get(accountname, "remoteuser"),
|
||||
config.get(accountname, "remotehost")))
|
||||
|
||||
def syncingmessages(s, sr, sf, dr, df):
|
||||
if s.verbose:
|
||||
UIBase.syncingmessages(s, sr, sf, dr, df)
|
||||
|
||||
def loadmessagelist(s, repos, folder):
|
||||
if s.verbose:
|
||||
UIBase.syncingmessages(s, repos, folder)
|
||||
|
||||
def messagelistloaded(s, repos, folder, count):
|
||||
if s.verbose:
|
||||
UIBase.messagelistloaded(s, repos, folder, count)
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
s.iswaiting = 1
|
||||
try:
|
||||
UIBase.sleep(s, sleepsecs)
|
||||
finally:
|
||||
s.iswaiting = 0
|
||||
|
||||
def mainException(s):
|
||||
if isinstance(sys.exc_info()[1], KeyboardInterrupt) and \
|
||||
s.iswaiting:
|
||||
sys.stdout.write("Timer interrupted at user request; program terminating. \n")
|
||||
s.terminate()
|
||||
else:
|
||||
UIBase.mainException(s)
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
if remainingsecs > 0:
|
||||
sys.stdout.write("Next sync in %d:%02d (press Enter to sync now, Ctrl-C to abort) \r" % \
|
||||
(remainingsecs / 60, remainingsecs % 60))
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
sys.stdout.write("Wait done, proceeding with sync.... \n")
|
||||
|
||||
if sleepsecs > 0:
|
||||
if len(select.select([sys.stdin], [], [], sleepsecs)[0]):
|
||||
sys.stdin.readline()
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
@ -1,298 +0,0 @@
|
||||
# Tk UI
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from Tkinter import *
|
||||
from threading import *
|
||||
import thread, traceback, time
|
||||
from StringIO import StringIO
|
||||
from ScrolledText import ScrolledText
|
||||
from offlineimap import threadutil, version
|
||||
from Queue import Queue
|
||||
from UIBase import UIBase
|
||||
|
||||
class PasswordDialog:
|
||||
def __init__(self, accountname, config, master=None):
|
||||
self.top = Toplevel(master)
|
||||
self.top.title(version.productname + " Password Entry")
|
||||
self.label = Label(self.top,
|
||||
text = "%s: Enter password for %s on %s: " % \
|
||||
(accountname, config.get(accountname, "remoteuser"),
|
||||
config.get(accountname, "remotehost")))
|
||||
self.label.pack()
|
||||
|
||||
self.entry = Entry(self.top, show='*')
|
||||
self.entry.bind("<Return>", self.ok)
|
||||
self.entry.pack()
|
||||
self.entry.focus_force()
|
||||
|
||||
self.button = Button(self.top, text = "OK", command=self.ok)
|
||||
self.button.pack()
|
||||
|
||||
self.entry.focus_force()
|
||||
self.top.wait_window(self.label)
|
||||
|
||||
def ok(self, args = None):
|
||||
self.password = self.entry.get()
|
||||
self.top.destroy()
|
||||
|
||||
def getpassword(self):
|
||||
return self.password
|
||||
|
||||
class TextOKDialog:
|
||||
def __init__(self, title, message, blocking = 1, master = None):
|
||||
if not master:
|
||||
self.top = Tk()
|
||||
else:
|
||||
self.top = Toplevel(master)
|
||||
self.top.title(title)
|
||||
self.text = ScrolledText(self.top, font = "Courier 10")
|
||||
self.text.pack()
|
||||
self.text.insert(END, message)
|
||||
self.text['state'] = DISABLED
|
||||
self.button = Button(self.top, text = "OK", command=self.ok)
|
||||
self.button.pack()
|
||||
|
||||
if blocking:
|
||||
self.top.wait_window(self.button)
|
||||
|
||||
def ok(self):
|
||||
self.top.destroy()
|
||||
|
||||
|
||||
|
||||
class ThreadFrame(Frame):
|
||||
def __init__(self, master=None):
|
||||
self.threadextraframe = None
|
||||
self.thread = currentThread()
|
||||
self.threadid = thread.get_ident()
|
||||
Frame.__init__(self, master, relief = RIDGE, borderwidth = 2)
|
||||
self.pack(fill = 'x')
|
||||
self.threadlabel = Label(self, foreground = '#FF0000',
|
||||
text ="Thread %d (%s)" % (self.threadid,
|
||||
self.thread.getName()))
|
||||
self.threadlabel.pack()
|
||||
self.setthread(currentThread())
|
||||
|
||||
self.account = "Unknown"
|
||||
self.mailbox = "Unknown"
|
||||
self.loclabel = Label(self,
|
||||
text = "Account/mailbox information unknown")
|
||||
#self.loclabel.pack()
|
||||
|
||||
self.updateloclabel()
|
||||
|
||||
self.message = Label(self, text="Messages will appear here.\n",
|
||||
foreground = '#0000FF')
|
||||
self.message.pack(fill = 'x')
|
||||
|
||||
def setthread(self, newthread):
|
||||
if newthread:
|
||||
self.threadlabel['text'] = newthread.getName()
|
||||
else:
|
||||
self.threadlabel['text'] = "No thread"
|
||||
self.destroythreadextraframe()
|
||||
|
||||
def destroythreadextraframe(self):
|
||||
if self.threadextraframe:
|
||||
self.threadextraframe.destroy()
|
||||
self.threadextraframe = None
|
||||
|
||||
def getthreadextraframe(self):
|
||||
if self.threadextraframe:
|
||||
return self.threadextraframe
|
||||
self.threadextraframe = Frame(self)
|
||||
self.threadextraframe.pack(fill = 'x')
|
||||
return self.threadextraframe
|
||||
|
||||
def setaccount(self, account):
|
||||
self.account = account
|
||||
self.mailbox = "Unknown"
|
||||
self.updateloclabel()
|
||||
|
||||
def setmailbox(self, mailbox):
|
||||
self.mailbox = mailbox
|
||||
self.updateloclabel()
|
||||
|
||||
def updateloclabel(self):
|
||||
self.loclabel['text'] = "Processing %s: %s" % (self.account,
|
||||
self.mailbox)
|
||||
|
||||
def appendmessage(self, newtext):
|
||||
self.message['text'] += "\n" + newtext
|
||||
|
||||
def setmessage(self, newtext):
|
||||
self.message['text'] = newtext
|
||||
|
||||
|
||||
class TkUI(UIBase):
|
||||
def __init__(self, verbose = 0):
|
||||
self.verbose = verbose
|
||||
|
||||
def isusable(s):
|
||||
try:
|
||||
Tk().destroy()
|
||||
return 1
|
||||
except TclError:
|
||||
return 0
|
||||
|
||||
def _createTopWindow(self):
|
||||
self.top = Tk()
|
||||
self.top.title(version.productname + " " + version.versionstr)
|
||||
self.threadframes = {}
|
||||
self.availablethreadframes = []
|
||||
self.tflock = Lock()
|
||||
self.notdeleted = 1
|
||||
|
||||
t = threadutil.ExitNotifyThread(target = self._runmainloop,
|
||||
name = "Tk Mainloop")
|
||||
t.setDaemon(1)
|
||||
t.start()
|
||||
|
||||
t = threadutil.ExitNotifyThread(target = self.idlevacuum,
|
||||
name = "Tk idle vacuum")
|
||||
t.setDaemon(1)
|
||||
t.start()
|
||||
|
||||
def _runmainloop(s):
|
||||
s.top.mainloop()
|
||||
s.notdeleted = 0
|
||||
|
||||
def getpass(s, accountname, config):
|
||||
pd = PasswordDialog(accountname, config)
|
||||
return pd.getpassword()
|
||||
|
||||
def gettf(s):
|
||||
threadid = thread.get_ident()
|
||||
s.tflock.acquire()
|
||||
try:
|
||||
if threadid in s.threadframes:
|
||||
return s.threadframes[threadid]
|
||||
if len(s.availablethreadframes):
|
||||
tf = s.availablethreadframes.pop(0)
|
||||
tf.setthread(currentThread())
|
||||
else:
|
||||
tf = ThreadFrame(s.top)
|
||||
s.threadframes[threadid] = tf
|
||||
return tf
|
||||
finally:
|
||||
s.tflock.release()
|
||||
|
||||
def _msg(s, msg):
|
||||
s.gettf().setmessage(msg)
|
||||
|
||||
def threadExited(s, thread):
|
||||
threadid = thread.threadid
|
||||
s.tflock.acquire()
|
||||
if threadid in s.threadframes:
|
||||
tf = s.threadframes[threadid]
|
||||
tf.setthread(None)
|
||||
tf.setaccount("Unknown")
|
||||
tf.setmessage("Idle")
|
||||
s.availablethreadframes.append(tf)
|
||||
del s.threadframes[threadid]
|
||||
s.tflock.release()
|
||||
|
||||
def idlevacuum(s):
|
||||
while s.notdeleted:
|
||||
time.sleep(10)
|
||||
s.tflock.acquire()
|
||||
while len(s.availablethreadframes):
|
||||
tf = s.availablethreadframes.pop()
|
||||
tf.destroy()
|
||||
s.tflock.release()
|
||||
|
||||
def threadException(s, thread):
|
||||
msg = "Thread '%s' terminated with exception:\n%s" % \
|
||||
(thread.getName(), thread.getExitStackTrace())
|
||||
print msg
|
||||
|
||||
s.top.destroy()
|
||||
s.top = None
|
||||
TextOKDialog("Thread Exception", msg)
|
||||
s.terminate(100)
|
||||
|
||||
def mainException(s):
|
||||
sbuf = StringIO()
|
||||
traceback.print_exc(file = sbuf)
|
||||
msg = "Main program terminated with exception:\n" + sbuf.getvalue()
|
||||
print msg
|
||||
|
||||
s.top.destroy()
|
||||
s.top = None
|
||||
TextOKDialog("Main Program Exception", msg)
|
||||
|
||||
def warn(s, msg):
|
||||
TextOKDialog("OfflineIMAP Warning", msg)
|
||||
|
||||
def init_banner(s):
|
||||
s._createTopWindow()
|
||||
s._msg(version.productname + " " + version.versionstr + ", " +\
|
||||
version.copyright)
|
||||
tf = s.gettf().getthreadextraframe()
|
||||
|
||||
def showlicense():
|
||||
TextOKDialog(version.productname + " License",
|
||||
version.bigcopyright + "\n" +
|
||||
version.homepage + "\n\n" + version.license,
|
||||
blocking = 0, master = tf)
|
||||
b = Button(tf, text = "About", command = showlicense)
|
||||
b.pack(side = LEFT)
|
||||
|
||||
b = Button(tf, text = "Exit", command = s.terminate)
|
||||
b.pack(side = RIGHT)
|
||||
|
||||
def deletingmessages(s, uidlist, destlist):
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting %d messages in %s" % (len(uidlist), ds))
|
||||
|
||||
def _sleep_cancel(s, args = None):
|
||||
s.sleeping_abort = 1
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
s.sleeping_abort = 0
|
||||
tf = s.gettf().getthreadextraframe()
|
||||
|
||||
sleepbut = Button(tf, text = 'Sync immediately',
|
||||
command = s._sleep_cancel)
|
||||
sleepbut.pack()
|
||||
UIBase.sleep(s, sleepsecs)
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
if remainingsecs:
|
||||
s._msg("Next sync in %d:%02d" % (remainingsecs / 60,
|
||||
remainingsecs % 60))
|
||||
else:
|
||||
s._msg("Wait done; synchronizing now.")
|
||||
s.gettf().destroythreadextraframe()
|
||||
time.sleep(sleepsecs)
|
||||
return s.sleeping_abort
|
||||
|
||||
################################################## Copied from TTY
|
||||
|
||||
def syncingmessages(s, sr, sf, dr, df):
|
||||
if s.verbose:
|
||||
UIBase.syncingmessages(s, sr, sf, dr, df)
|
||||
|
||||
def loadmessagelist(s, repos, folder):
|
||||
if s.verbose:
|
||||
UIBase.syncingmessages(s, repos, folder)
|
||||
|
||||
def messagelistloaded(s, repos, folder, count):
|
||||
if s.verbose:
|
||||
UIBase.messagelistloaded(s, repos, folder, count)
|
||||
|
@ -1,171 +0,0 @@
|
||||
# UI base class
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from offlineimap import repository
|
||||
import offlineimap.version
|
||||
import re, time, sys, traceback
|
||||
from StringIO import StringIO
|
||||
|
||||
class UIBase:
|
||||
################################################## UTILS
|
||||
def _msg(s, msg):
|
||||
"""Generic tool called when no other works."""
|
||||
raise NotImplementedException
|
||||
|
||||
def warn(s, msg):
|
||||
s._msg("WARNING: " + msg)
|
||||
|
||||
def getnicename(s, object):
|
||||
prelimname = str(object.__class__).split('.')[-1]
|
||||
# Strip off extra stuff.
|
||||
return re.sub('(Folder|Repository)', '', prelimname)
|
||||
|
||||
def isusable(s):
|
||||
"""Returns true if this UI object is usable in the current
|
||||
environment. For instance, an X GUI would return true if it's
|
||||
being run in X with a valid DISPLAY setting, and false otherwise."""
|
||||
return 1
|
||||
|
||||
################################################## INPUT
|
||||
|
||||
def getpass(s, accountname, config):
|
||||
raise NotImplementedException
|
||||
|
||||
def folderlist(s, list):
|
||||
return ', '.join(["%s[%s]" % (s.getnicename(x), x.getname()) for x in list])
|
||||
|
||||
################################################## MESSAGES
|
||||
|
||||
def init_banner(s):
|
||||
"""Called when the UI starts. Must be called before any other UI
|
||||
call except isusable(). Displays the copyright banner. This is
|
||||
where the UI should do its setup -- TK, for instance, would
|
||||
create the application window here."""
|
||||
s._msg(offlineimap.version.banner)
|
||||
|
||||
def acct(s, accountname):
|
||||
s._msg("***** Processing account %s" % accountname)
|
||||
|
||||
def syncfolders(s, srcrepos, destrepos):
|
||||
s._msg("Copying folder structure from %s to %s" % \
|
||||
(s.getnicename(srcrepos), s.getnicename(destrepos)))
|
||||
|
||||
############################## Folder syncing
|
||||
def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):
|
||||
"""Called when a folder sync operation is started."""
|
||||
s._msg("Syncing %s: %s -> %s" % (srcfolder.getname(),
|
||||
s.getnicename(srcrepos),
|
||||
s.getnicename(destrepos)))
|
||||
|
||||
def validityproblem(s, folder):
|
||||
s.warn("UID validity problem for folder %s; skipping it" % \
|
||||
folder.getname())
|
||||
|
||||
def loadmessagelist(s, repos, folder):
|
||||
s._msg("Loading message list for %s[%s]" % (s.getnicename(repos),
|
||||
folder.getname()))
|
||||
|
||||
def messagelistloaded(s, repos, folder, count):
|
||||
s._msg("Message list for %s[%s] loaded: %d messages" % \
|
||||
(s.getnicename(repos), folder.getname(), count))
|
||||
|
||||
############################## Message syncing
|
||||
|
||||
def syncingmessages(s, sr, sf, dr, df):
|
||||
s._msg("Syncing messages %s[%s] -> %s[%s]" % (s.getnicename(sr),
|
||||
sf.getname(),
|
||||
s.getnicename(dr),
|
||||
df.getname()))
|
||||
|
||||
def copyingmessage(s, uid, src, destlist):
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Copy message %d %s[%s] -> %s" % (uid, s.getnicename(src),
|
||||
src.getname(), ds))
|
||||
|
||||
def deletingmessage(s, uid, destlist):
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting message %d in %s" % (uid, ds))
|
||||
|
||||
def deletingmessages(s, uidlist, destlist):
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting %d messages (%s) in %s" % \
|
||||
(len(uidlist),
|
||||
", ".join([str(u) for u in uidlist]),
|
||||
ds))
|
||||
|
||||
def addingflags(s, uid, flags, destlist):
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Adding flags %s to message %d on %s" % \
|
||||
(", ".join(flags), uid, ds))
|
||||
|
||||
def deletingflags(s, uid, flags, destlist):
|
||||
ds = s.folderlist(destlist)
|
||||
s._msg("Deleting flags %s to message %d on %s" % \
|
||||
(", ".join(flags), uid, ds))
|
||||
|
||||
################################################## Threads
|
||||
|
||||
def threadException(s, thread):
|
||||
"""Called when a thread has terminated with an exception.
|
||||
The argument is the ExitNotifyThread that has so terminated."""
|
||||
s._msg("Thread '%s' terminated with exception:\n%s" % \
|
||||
(thread.getName(), thread.getExitStackTrace()))
|
||||
s.terminate(100)
|
||||
|
||||
def mainException(s):
|
||||
sbuf = StringIO()
|
||||
traceback.print_exc(file = sbuf)
|
||||
s._msg("Main program terminated with exception:\n" +
|
||||
sbuf.getvalue())
|
||||
|
||||
def terminate(s, exitstatus = 0):
|
||||
"""Called to terminate the application."""
|
||||
sys.exit(exitstatus)
|
||||
|
||||
def threadExited(s, thread):
|
||||
"""Called when a thread has exited normally. Many UIs will
|
||||
just ignore this."""
|
||||
pass
|
||||
|
||||
################################################## Other
|
||||
|
||||
def sleep(s, sleepsecs):
|
||||
"""This function does not actually output anything, but handles
|
||||
the overall sleep, dealing with updates as necessary. It will,
|
||||
however, call sleeping() which DOES output something.
|
||||
|
||||
Returns 0 if timeout expired, 1 if there is a request to cancel
|
||||
the timer, and 2 if there is a request to abort the program."""
|
||||
|
||||
abortsleep = 0
|
||||
while sleepsecs > 0 and not abortsleep:
|
||||
abortsleep = s.sleeping(1, sleepsecs)
|
||||
sleepsecs -= 1
|
||||
s.sleeping(0, 0) # Done sleeping.
|
||||
return abortsleep
|
||||
|
||||
def sleeping(s, sleepsecs, remainingsecs):
|
||||
"""Sleep for sleepsecs, remainingsecs to go.
|
||||
If sleepsecs is 0, indicates we're done sleeping.
|
||||
|
||||
Return 0 for normal sleep, or 1 to indicate a request
|
||||
to sync immediately."""
|
||||
s._msg("Next refresh in %d seconds" % remainingsecs)
|
||||
if sleepsecs > 0:
|
||||
time.sleep(sleepsecs)
|
||||
return 0
|
@ -1,32 +0,0 @@
|
||||
# UI module directory
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
import UIBase
|
||||
try:
|
||||
import TTY
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
import Tkinter
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
import Tk
|
||||
|
||||
import detector
|
@ -1,40 +0,0 @@
|
||||
# UI base class
|
||||
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from offlineimap.ui import *
|
||||
import sys
|
||||
|
||||
def findUI(config):
|
||||
uistrlist = ['Tk.TkUI', 'TTY.TTYUI']
|
||||
if config.has_option("general", "ui"):
|
||||
uistrlist = config.get("general", "ui").replace(" ", "").split(",")
|
||||
for uistr in uistrlist:
|
||||
uimod = getUImod(uistr)
|
||||
if uimod:
|
||||
uiinstance = uimod()
|
||||
if uiinstance.isusable():
|
||||
return uiinstance
|
||||
sys.stderr.write("ERROR: No UIs were found usable!\n")
|
||||
sys.exit(200)
|
||||
|
||||
def getUImod(uistr):
|
||||
try:
|
||||
uimod = eval(uistr)
|
||||
except (AttributeError, NameError):
|
||||
return None
|
||||
return uimod
|
@ -1,84 +0,0 @@
|
||||
productname = 'OfflineIMAP'
|
||||
versionstr = "3.0.2"
|
||||
revno = long('$Rev: 128 $'[6:-2])
|
||||
revstr = "Rev %d" % revno
|
||||
datestr = '$Date: 2002-07-17 13:06:27 -0500 (Wed, 17 Jul 2002) $'
|
||||
|
||||
versionlist = versionstr.split(".")
|
||||
major = versionlist[0]
|
||||
minor = versionlist[1]
|
||||
patch = versionlist[2]
|
||||
copyright = "Copyright (C) 2002 John Goerzen"
|
||||
author = "John Goerzen"
|
||||
author_email = "jgoerzen@complete.org"
|
||||
description = "Disconnected Universal IMAP Mail Synchronization/Reader Support"
|
||||
bigcopyright = """%(productname)s %(versionstr)s (%(revstr)s)
|
||||
%(copyright)s <%(author_email)s>""" % locals()
|
||||
|
||||
banner = bigcopyright + """
|
||||
This software comes with ABSOLUTELY NO WARRANTY; see the file
|
||||
COPYING for details. This is free software, and you are welcome
|
||||
to distribute it under the conditions laid out in COPYING."""
|
||||
|
||||
homepage = "http://www.quux.org/devel/offlineimap"
|
||||
homegopher = "gopher://quux.org/1/devel/offlineimap"
|
||||
license = """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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA"""
|
||||
|
||||
cmdhelp = """
|
||||
offlineimap [ -1 ] [ -a accountlist ] [ -c configfile ]
|
||||
[ -d ] [ -u interface ]
|
||||
|
||||
offlineimap -h | --help
|
||||
|
||||
-1 Disable all multithreading operations and use
|
||||
solely a single-thread sync. This effectively sets
|
||||
the maxsyncaccounts and all maxconnections configu-
|
||||
ration file variables to 1.
|
||||
|
||||
-a accountlist
|
||||
Overrides the accounts section in the config file.
|
||||
Lets you specify a particular account or set of
|
||||
accounts to sync without having to edit the config
|
||||
file. You might use this to exclude certain
|
||||
accounts, or to sync some accounts that you nor-
|
||||
mally prefer not to.
|
||||
|
||||
-c configfile
|
||||
Specifies a configuration file to use in lieu of
|
||||
the default, ~/.offlineimaprc.
|
||||
|
||||
-d Enables IMAP protocol stream and parsing debugging.
|
||||
This is useful if you are trying to track down a
|
||||
malfunction or figure out what is going on under
|
||||
the hood. I suggest that you use this with -1 in
|
||||
order to make the results more sensible. Note that
|
||||
this output will contain full IMAP protocol in
|
||||
plain text, including passwords, so take care to
|
||||
remove that from the debugging output before send-
|
||||
ing it to anyone else.
|
||||
|
||||
-h, --help
|
||||
Show summary of options.
|
||||
|
||||
-u interface
|
||||
Specifies an alternative user interface module to
|
||||
use. This overrides the default specified in the
|
||||
configuration file. The UI specified with -u will
|
||||
be forced to be used, even if its isuable() method
|
||||
states that it cannot be. Use this option with
|
||||
care.
|
||||
"""
|
Reference in New Issue
Block a user