]> code.delx.au - offlineimap/blob - offlineimap/repository/Base.py
Synchronize newly created folders both ways
[offlineimap] / offlineimap / repository / Base.py
1 # Base repository support
2 # Copyright (C) 2002-2007 John Goerzen
3 # <jgoerzen@complete.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (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 St, Fifth Floor, Boston, MA 02110-1301 USA
18
19 from offlineimap import CustomConfig
20 from offlineimap.ui import UIBase
21 import os.path
22
23 def LoadRepository(name, account, reqtype):
24 from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository
25 from offlineimap.repository.Maildir import MaildirRepository
26 if reqtype == 'remote':
27 # For now, we don't support Maildirs on the remote side.
28 typemap = {'IMAP': IMAPRepository}
29 elif reqtype == 'local':
30 typemap = {'IMAP': MappedIMAPRepository,
31 'Maildir': MaildirRepository}
32 else:
33 raise ValueError, "Request type %s not supported" % reqtype
34 config = account.getconfig()
35 repostype = config.get('Repository ' + name, 'type').strip()
36 return typemap[repostype](name, account)
37
38 class BaseRepository(CustomConfig.ConfigHelperMixin):
39 def __init__(self, reposname, account):
40 self.account = account
41 self.config = account.getconfig()
42 self.name = reposname
43 self.localeval = account.getlocaleval()
44 self.accountname = self.account.getname()
45 self.uiddir = os.path.join(self.config.getmetadatadir(), 'Repository-' + self.name)
46 if not os.path.exists(self.uiddir):
47 os.mkdir(self.uiddir, 0700)
48 self.mapdir = os.path.join(self.uiddir, 'UIDMapping')
49 if not os.path.exists(self.mapdir):
50 os.mkdir(self.mapdir, 0700)
51 self.uiddir = os.path.join(self.uiddir, 'FolderValidity')
52 if not os.path.exists(self.uiddir):
53 os.mkdir(self.uiddir, 0700)
54
55 # The 'restoreatime' config parameter only applies to local Maildir
56 # mailboxes.
57 def restore_atime(self):
58 if self.config.get('Repository ' + self.name, 'type').strip() != \
59 'Maildir':
60 return
61
62 if not self.config.has_option('Repository ' + self.name, 'restoreatime') or not self.config.getboolean('Repository ' + self.name, 'restoreatime'):
63 return
64
65 return self.restore_folder_atimes()
66
67 def connect(self):
68 """Establish a connection to the remote, if necessary. This exists
69 so that IMAP connections can all be established up front, gathering
70 passwords as needed. It was added in order to support the
71 error recovery -- we need to connect first outside of the error
72 trap in order to validate the password, and that's the point of
73 this function."""
74 pass
75
76 def holdordropconnections(self):
77 pass
78
79 def dropconnections(self):
80 pass
81
82 def getaccount(self):
83 return self.account
84
85 def getname(self):
86 return self.name
87
88 def getuiddir(self):
89 return self.uiddir
90
91 def getmapdir(self):
92 return self.mapdir
93
94 def getaccountname(self):
95 return self.accountname
96
97 def getsection(self):
98 return 'Repository ' + self.name
99
100 def getconfig(self):
101 return self.config
102
103 def getlocaleval(self):
104 return self.account.getlocaleval()
105
106 def getfolders(self):
107 """Returns a list of ALL folders on this server."""
108 return []
109
110 def forgetfolders(self):
111 """Forgets the cached list of folders, if any. Useful to run
112 after a sync run."""
113 pass
114
115 def getsep(self):
116 raise NotImplementedError
117
118 def makefolder(self, foldername):
119 raise NotImplementedError
120
121 def deletefolder(self, foldername):
122 raise NotImplementedError
123
124 def getfolder(self, foldername):
125 raise NotImplementedError
126
127 def syncfoldersto(self, dest, status):
128 """Syncs the folders in this repository to those in dest.
129 It does NOT sync the contents of those folders."""
130 src = self
131 srcfolders = src.getfolders()
132 destfolders = dest.getfolders()
133
134 # Create hashes with the names, but convert the source folders
135 # to the dest folder's sep.
136
137 srchash = {}
138 for folder in srcfolders:
139 if (folder.getvisiblename()[-2:] == src.getsep() + "."):
140 continue
141 srchash[folder.getvisiblename().replace(src.getsep(), dest.getsep())] = \
142 folder
143 desthash = {}
144 for folder in destfolders:
145 desthash[folder.getvisiblename()] = folder
146
147 #
148 # Find new folders.
149 #
150
151 ignoredfolders = []
152 newfolders = []
153
154 for key in srchash.keys():
155 if not key in desthash:
156 srckey = key.replace(dest.getsep(),src.getsep())
157 if status.getfolder(key.replace(dest.getsep(),status.getsep())).isnewfolder():
158 dest.makefolder(key)
159 newfolders.append(srckey)
160 else:
161 UIBase.getglobalui().ignorefolder (key, src, dest)
162 ignoredfolders.append(srckey)
163
164 #
165 # Find deleted folders.
166 #
167 # We don't delete folders right now.
168
169 #for key in desthash.keys():
170 # if not key in srchash:
171 # dest.deletefolder(key)
172
173 return (ignoredfolders,newfolders)
174
175 ##### Keepalive
176
177 def startkeepalive(self):
178 """The default implementation will do nothing."""
179 pass
180
181 def stopkeepalive(self, abrupt = 0):
182 """Stop keep alive. If abrupt is 1, stop it but don't bother waiting
183 for the threads to terminate."""
184 pass
185