jaromail

a commandline tool to easily and privately handle your e-mail
git clone git://parazyd.org/jaromail.git
Log | Files | Refs | Submodules | README

emlx2maildir.py (6336B)


      1 #!/usr/bin/env python
      2 
      3 # Copyright (C) 2009-2013 Mike Laiosa <mike@laiosa.org> and others.
      4 #
      5 # This program is free software; you can redistribute it and/or
      6 # modify it under the terms of the GNU General Public License
      7 # as published by the Free Software Foundation; either version 2
      8 # of the License, or (at your option) any later version.
      9 # 
     10 # This program is distributed in the hope that it will be useful,
     11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 # GNU General Public License for more details.
     14 # 
     15 # You should have received a copy of the GNU General Public License
     16 # along with this program; if not, write to the Free Software
     17 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
     18 
     19 import optparse
     20 import xml.sax, xml.sax.handler
     21 import re, os, os.path, socket, time
     22 
     23 class PlistHandler(xml.sax.handler.ContentHandler):
     24 	def __init__(self):
     25 		xml.sax.handler.ContentHandler.__init__(self)
     26 		self.key = None
     27 		def set_top(k,v):
     28 			self.top = v
     29 		self.stack = [set_top]
     30 	def generate(self, value):
     31 		self.stack[-1](self.key, value)
     32 	def startElement(self, name, attrs):
     33 		self.elem = name
     34 		self.value = ""
     35 		if name == "array":
     36 			ar = []
     37 			self.stack.append(ar)
     38 			self.stack.append(lambda k,v: ar.append(v))
     39 		elif name == "dict":
     40 			d = {}
     41 			def add(k, v):
     42 				d[k] = v
     43 			self.stack.append(d)
     44 			self.stack.append(add)
     45 	def endElement(self, name):
     46 		if name == "string":
     47 			self.generate(self.value)
     48 		elif name == "integer":
     49 			self.generate(long(self.value))
     50 		elif name == "real":
     51 			self.generate(float(self.value))
     52 		elif name == "key":
     53 			self.key = self.value
     54 		elif name in ("dict", "array"):
     55 			x = self.stack[-2]
     56 			self.stack = self.stack[:-2]
     57 			self.generate(x)
     58 		elif name in ("plist", "data"):
     59 			pass
     60 		else:
     61 			print "Unknown tag: %s" % name
     62 	def characters(self, chars):
     63 		self.value += chars
     64 
     65 def parse_plist(plist_xml):
     66 	# Remove any DOCTYPE declarations, to stop the XML parser from trying to
     67 	# download the DTD.
     68 	plist_xml = re.sub(r'<!DOCTYPE.*?"\s*>', "", plist_xml)
     69 	p = PlistHandler()
     70 	xml.sax.parseString(plist_xml, p)
     71 	return p.top
     72 
     73 FL_READ = (1<<0)
     74 FL_DELETED = (1<<1)
     75 FL_ANSWERED = (1<<2)
     76 FL_ENCRYPTED = (1<<3)
     77 FL_FLAGGED = (1<<4)
     78 FL_RECENT = (1<<5)
     79 FL_DRAFT = (1<<6)
     80 FL_INITIAL = (1<<7)
     81 FL_FORWARDED = (1<<8)
     82 FL_REDIRECTED = (1<<9)
     83 FL_SIGNED = (1<<23)
     84 FL_IS_JUNK = (1<<24)
     85 FL_IS_NOT_JUNK = (1<<25)
     86 FL_JUNK_LEVEL_RECORDED = (1<<29)
     87 FL_HIGHLIGHT_IN_TOC = (1<<30)
     88 
     89 flag_mapping = [
     90 	(FL_DRAFT, "D"),
     91 	(FL_FLAGGED, "F"),
     92 	((FL_FORWARDED | FL_REDIRECTED), "P"),
     93 	(FL_ANSWERED, "R"),
     94 	(FL_READ, "S"),
     95 	(FL_DELETED, "T"),
     96 ]
     97 
     98 hostname = socket.gethostname()
     99 pid = os.getpid()
    100 gSeq = 0
    101 
    102 def md_filename(date, flags):
    103 	global gSeq
    104 	gSeq += 1
    105 	return "%d.M%dP%dQ%d.%s:2,%s" % (date, time.time(), pid, gSeq, hostname, flags)
    106 
    107 def convert_one(emlx_file, maildir):
    108 	contents = open(emlx_file, "rb").read()
    109 	boundry = contents.find("\x0a")
    110 	length = long(contents[:boundry])
    111 	body = contents[boundry+1:boundry+1+length]
    112 	metadata = parse_plist(contents[boundry+1+length:])
    113 
    114 	flags = ""
    115 	if "flags" in metadata:
    116 		for fl, let in flag_mapping:
    117 			if metadata['flags'] & fl:
    118 				flags += let
    119 
    120 	date = long(metadata.get('date-sent', time.time()))
    121 	filename = md_filename(date, flags)
    122 	tmp_name = os.path.join(maildir, "tmp", filename)
    123 	cur_name = os.path.join(maildir, "cur", filename)
    124 	open(tmp_name, "wb").write(body)
    125 	os.rename(tmp_name, cur_name)
    126 	os.utime(cur_name, (date,date))
    127 
    128 def emlx_message_dir(emlx_dir):
    129 	msg_dir = os.path.join(emlx_dir + ".mbox", "Messages")
    130 	if not os.path.isdir(msg_dir):
    131 		msg_dir = os.path.join(emlx_dir + ".imapmbox", "Messages")
    132 	if not os.path.isdir(msg_dir):
    133 		return None
    134 	return msg_dir
    135 
    136 def emlx_message_dirs(emlx_dir):
    137 	for x in os.listdir(emlx_dir):
    138 		suffixes = [".sbd", ".mbox", ".imapmbox"]
    139 		search = True
    140 		for s in suffixes:
    141 			if x.endswith(s):
    142 				search = False
    143 		if x == "Messages":
    144 			msg_dir = os.path.join(emlx_dir,"Messages")
    145 			yield msg_dir
    146 		elif search:
    147 			subfolder = os.path.join(emlx_dir, x)
    148 			if	os.path.isdir(subfolder):
    149 				print "Recursing into %r" % (subfolder)
    150 				for tmp in emlx_message_dirs(subfolder):
    151 					yield tmp
    152 
    153 def emlx_messages(emlx_dir):
    154 	if os.path.isdir(emlx_dir + ".mbox"):
    155 		for msg_dir in emlx_message_dirs(emlx_dir + ".mbox"):
    156 			for x in os.listdir(msg_dir):
    157 				if x.endswith(".emlx") and x[0] != '.':
    158 					yield os.path.join(msg_dir, x)
    159 
    160 def emlx_subfolders(emlx_dir):
    161 	if not os.path.isdir(emlx_dir):
    162 		if os.path.isdir(emlx_dir + ".sbd"):
    163 			emlx_dir = ".sbd"
    164 		else:
    165 			return
    166 	for x in os.listdir(emlx_dir):
    167 		suffixes = [".sbd", ".mbox", ".imapmbox"]
    168 		for s in suffixes:
    169 			if x.endswith(s):
    170 				subfolder = os.path.join(emlx_dir, x[:-len(s)])
    171 				for tmp in emlx_subfolders(os.path.join(emlx_dir, x)):
    172 					yield tmp
    173 				yield subfolder
    174 
    175 def maildirmake(dir):
    176 	for s in ["cur", "new", "tmp"]:
    177 		if not os.path.exists(os.path.join(dir, s)):
    178 			os.makedirs(os.path.join(dir, s))
    179 
    180 def remove_slash(s):
    181 	if len(s) and s[-1] == '/':
    182 		return s[:-1]
    183 	else:
    184 		return s
    185 
    186 def main():
    187 	parser = optparse.OptionParser(usage="Usage: %prog [options] emlx_folder maildir")
    188 	parser.add_option("-r", "--recursive", action="store_true", help="Recurse into subfolders")
    189 	parser.add_option("-q", "--quiet", action="store_true", help="Only print error output")
    190 	parser.add_option("--dry-run", action="store_true", help="Don't do anything")
    191 	parser.add_option("--verbose", action="store_true", help="Displays lots of stuff")
    192 	opts, args = parser.parse_args()
    193 
    194 	def P(s):
    195 		if not opts.quiet:
    196 			print s
    197 	def V(s):
    198 		if opts.dry_run or opts.verbose:
    199 			P(s)
    200 
    201 	def dry(s, act, *args, **kwargs):
    202 		V(s)
    203 		if not opts.dry_run:
    204 			return act(*args, **kwargs)
    205 
    206 	if len(args) != 2:
    207 		parser.error("Not enough arguments")
    208 
    209 	tasks = [(remove_slash(args[0]), args[1] + '/')]
    210 	while len(tasks):
    211 		emlx_folder, maildir = tasks[-1]
    212 		
    213 		P("Converting %r -> %r" % (emlx_folder, maildir))
    214 		tasks = tasks[:-1]
    215 		dry("Making maildir %r" % maildir, maildirmake, maildir)
    216 		for msg in emlx_messages(emlx_folder):
    217 			dry("Converting message %r" % msg, convert_one, msg, maildir)
    218 		if opts.recursive:
    219 			for f in emlx_subfolders(emlx_folder):
    220 				tasks.append((f, maildir + "." + os.path.basename(f)))
    221 
    222 if __name__ == "__main__":
    223 	main()