c704142a1185f7e3dfd40c327049a526ea1ec81f
[imap-restore-mail] / src / file_iterator.py
1 '''
2 restore-mail-inject.py - Tool to inject mails via IMAP
3
4 Copyright (c) 2012 Intra2net AG
5 Author: Plamen Dimitrov and Thomas Jarosch
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 '''
17
18 import os
19 import re
20 import logging
21
22 MAIL_FILENAME = re.compile("^[0-9]+\.$")
23 MBOXFILE_LINE = re.compile("^(.*?)\t(?:\d )?default[\t ](.*)$")
24 ACL_STRING = re.compile("^(.*?)[\t ](.*?)\t(.*)$")
25
26 class FileIterator:
27     """This class iterates through the e-mail files."""
28
29     # class attributes
30     # mailboxes created during file traversal
31     created_mailboxes = None
32     # mailboxes to update during file traversal
33     acl_mailboxes = None
34
35     def __init__(self):
36         """Creates a connection and a user session."""
37
38         self.created_mailboxes = []
39         self.acl_mailboxes = []
40
41         return
42
43     @classmethod
44     def _message_read(cls, filename):
45         """Retrieves a message from the message file."""
46
47         try:
48             with open(filename, "r") as msgfile:
49                 message = msgfile.read()
50         except IOError:
51             logging.warning("Could not open the e-mail file %s", filename)
52             raise
53
54         return message
55
56     @classmethod
57     def load_mailbox_list(cls, mboxlistfile = ""):
58         """Load the list of mailboxes and acl rights for each from file."""
59
60         mboxdb = {}
61         if mboxlistfile != "":
62             try:
63                 with open(mboxlistfile, 'r') as acl_file:
64                     for line in acl_file:
65
66                         acls = {}
67                         try:
68                             linedata = MBOXFILE_LINE.match(line).groups()
69                         except AttributeError:
70                             logging.warning("Illegal line in mailbox list dump: %s", line)
71                             continue
72                         key = linedata[0]
73                         aclstr = linedata[1]
74
75                         # loop through acl rights string and build dictionary of users and rights
76                         while(aclstr != ""):
77                             try:
78                                 acldata = ACL_STRING.match(aclstr).groups()
79                             except AttributeError:
80                                 logging.warning("Illegal acl string in mailbox list dump: %s", line)
81                                 aclstr = ""
82                                 continue
83                             aclstr = acldata[2]
84                             acls[acldata[0]] = acldata[1]
85
86                         mboxdb[key] = acls
87             except IOError:
88                 logging.warning("Could not open mboxlist file %s", mboxlistfile)
89
90         return mboxdb
91
92     def load_mails(self, filepath, mailpath):
93         """Loads all e-mails from file hierarchy.
94         This recursive generator always returns a tuple of
95         the next found (e-mail, mailbox to store, internaldate)."""
96
97         logging.debug("Entered directory %s -> %s", filepath, mailpath)
98         try:
99             filepath = os.path.abspath(filepath)
100             os.chdir(filepath)
101         except OSError:
102             logging.warning("Can't open the directory %s", filepath)
103             return
104         # mark mailboxes that should be created
105         self.created_mailboxes.append(mailpath)
106         
107         subpaths = os.listdir(filepath)
108         for subpath in subpaths:
109             if subpath == "." or subpath == "..":
110                 continue
111             new_filepath = filepath + "/" + subpath
112
113             # if path is file validate name and inject
114             if (os.path.isfile(new_filepath)):
115                 if os.path.getsize(new_filepath) == 0:
116                     logging.info("Skipping empty file %s", subpath)
117                 else:
118                     if MAIL_FILENAME.match(subpath):
119                         logging.info("Injecting file %s", subpath)
120                         try:
121                             message = self._message_read(new_filepath)
122                             # suggest file modification date for internaldate
123                             yield (message, mailpath, os.path.getmtime(new_filepath))
124                         except IOError:
125                             logging.warning("Could not retrieve mail from the file %s", new_filepath)
126             else:
127
128                 # if path is directory do recursive call
129                 if os.path.isdir(new_filepath):
130                     # cyrus ^ vs . storage replacement
131                     subpath = subpath.replace("^", ".")
132                     new_mailpath = mailpath + "/" + subpath
133                     logging.debug("Inserting mails from directory %s into mailbox %s", new_filepath, new_mailpath)
134                     # load_mails($mboxdbref, $origuser, $targetuser)
135                     rcrs_generator = self.load_mails(new_filepath, new_mailpath)
136                     # you enter the generator in the for loop
137                     for rcr in rcrs_generator:
138                         yield rcr
139                     logging.debug("Done with directory %s and mailbox %s", new_filepath, new_mailpath)
140
141         # mark mailboxes that need acl update
142         self.acl_mailboxes.append(mailpath)
143
144         return