]> err.no Git - scalable-opengroupware.org/blob - SOGo/SoObjects/Mailer/SOGoMailManager.m
96a5026ee1dcf8e6205c2b17ab402bcb981c26b6
[scalable-opengroupware.org] / SOGo / SoObjects / Mailer / SOGoMailManager.m
1 /*
2   Copyright (C) 2004 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
6   OGo is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with OGo; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include "SOGoMailManager.h"
23 #include "common.h"
24
25 @interface SOGoMailConnectionEntry : NSObject
26 {
27 @public
28   NGImap4Client *client;
29   NSString      *password;
30   NSDate        *creationTime;
31   NSDictionary  *subfolders;
32 }
33
34 - (id)initWithClient:(NGImap4Client *)_client password:(NSString *)_pwd;
35
36 /* accessors */
37
38 - (NGImap4Client *)client;
39 - (BOOL)isValidPassword:(NSString *)_pwd;
40
41 - (NSDate *)creationTime;
42
43 - (void)cacheHierarchyResults:(NSDictionary *)_hierarchy;
44 - (NSDictionary *)cachedHierarchyResults;
45
46 @end
47
48 @implementation SOGoMailManager
49
50 static BOOL           debugOn    = YES;
51 static BOOL           poolingOff = NO;
52 static NSTimeInterval PoolScanInterval = 5 * 60;
53
54 + (void)initialize {
55   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
56   
57   debugOn    = [ud boolForKey:@"SOGoEnableIMAP4Debug"];
58   poolingOff = [ud boolForKey:@"SOGoDisableIMAP4Pooling"];
59 }
60
61 + (id)defaultMailManager {
62   static SOGoMailManager *manager = nil; // THREAD
63   if (manager == nil) 
64     manager = [[self alloc] init];
65   return manager;
66 }
67
68 - (id)init {
69   if ((self = [super init])) {
70     if (!poolingOff) {
71       self->urlToEntry = [[NSMutableDictionary alloc] initWithCapacity:256];
72     }
73     
74     self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
75                                 PoolScanInterval
76                               target:self selector:@selector(_garbageCollect:)
77                               userInfo:nil repeats:YES] retain];
78   }
79   return self;
80 }
81
82 - (void)dealloc {
83   if (self->gcTimer) [self->gcTimer invalidate];
84   [self->gcTimer release];
85   
86   [self->urlToEntry release];
87   [super dealloc];
88 }
89
90 /* cache */
91
92 - (id)cacheKeyForURL:(NSURL *)_url {
93   // protocol, user, host, port
94   return [NSString stringWithFormat:@"%@://%@@%@:%@",
95                    [_url scheme], [_url user], [_url host], [_url port]];
96 }
97
98 - (SOGoMailConnectionEntry *)entryForURL:(NSURL *)_url {
99   if (_url == nil)
100     return nil;
101   
102   return [self->urlToEntry objectForKey:[self cacheKeyForURL:_url]];
103 }
104 - (void)cacheEntry:(SOGoMailConnectionEntry *)_entry forURL:(NSURL *)_url {
105   if (_entry == nil) _entry = (id)[NSNull null];
106   [self->urlToEntry setObject:_entry forKey:[self cacheKeyForURL:_url]];
107 }
108
109 - (void)_garbageCollect:(NSTimer *)_timer {
110   // TODO: scan for old IMAP4 channels
111   [self debugWithFormat:@"should collect IMAP4 channels (%d active)",
112           [self->urlToEntry count]];
113 }
114
115 /* client object */
116
117 - (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd {
118   // TODO: move to some global IMAP4 connection pool manager
119   SOGoMailConnectionEntry *entry;
120   NGImap4Client *client;
121   NSDictionary  *result;
122   
123   if (_url == nil)
124     return nil;
125
126   /* check connection pool */
127   
128   if ((entry = [self entryForURL:_url]) != nil) {
129     if ([entry isValidPassword:_pwd]) {
130       [self debugWithFormat:@"reused IMAP4 connection for URL: %@", _url];
131       return [entry client];
132     }
133     
134     /* different password, password could have changed! */
135     entry = nil;
136   }
137   
138   /* setup connection and attempt login */
139   
140   if ((client = [NGImap4Client clientWithURL:_url]) == nil)
141     return nil;
142   
143   result = [client login:[_url user] password:_pwd];
144   if (![[result valueForKey:@"result"] boolValue]) {
145     [self logWithFormat:@"ERROR: IMAP4 login failed!"];
146     return nil;
147   }
148   
149   [self debugWithFormat:@"created new IMAP4 connection for URL: %@", _url];
150   
151   /* cache connection in pool */
152   
153   entry = [[SOGoMailConnectionEntry alloc] initWithClient:client 
154                                            password:_pwd];
155   [self cacheEntry:entry forURL:_url];
156   [entry release]; entry = nil;
157   
158   return client;
159 }
160
161 /* folder hierarchy */
162
163 - (NSArray *)_getDirectChildren:(NSArray *)_array folderName:(NSString *)_fn {
164   NSMutableArray *ma;
165   unsigned i, count, prefixlen;
166   
167   if ((count = [_array count]) < 2)
168     /* one entry is the folder itself, so we need at least two */
169     return [NSArray array];
170   
171   prefixlen = [_fn length] + 1;
172   ma = [NSMutableArray arrayWithCapacity:count];
173   for (i = 0; i < count; i++) {
174     NSString *p;
175     
176     p = [_array objectAtIndex:i];
177     if ([p length] <= prefixlen)
178       continue;
179     if (![p hasPrefix:_fn])
180       continue;
181     
182     /* cut of common part */
183     p = [p substringFromIndex:prefixlen];
184     
185     /* check whether the path is a sub-subfolder path */
186     if ([p rangeOfString:@"/"].length > 0)
187       continue;
188     
189     [ma addObject:p];
190   }
191   
192   [ma sortUsingSelector:@selector(compare:)];
193   return ma;
194 }
195
196 - (NSString *)imap4Separator {
197   return @".";
198 }
199
200 - (NSString *)imap4FolderNameForURL:(NSURL *)_url {
201   /* a bit hackish, but should be OK */
202   NSString *folderName;
203
204   if (_url == nil)
205     return nil;
206   
207   folderName = [_url path];
208   if ([folderName length] == 0)
209     return nil;
210   if ([folderName characterAtIndex:0] == '/')
211     folderName = [folderName substringFromIndex:1];
212   
213   return [[folderName pathComponents] componentsJoinedByString:
214                                         [self imap4Separator]];
215 }
216
217 - (NSArray *)extractSubfoldersForURL:(NSURL *)_url
218   fromResultSet:(NSDictionary *)_result
219 {
220   NSString     *folderName;
221   NSDictionary *result;
222   NSArray      *names;
223   NSArray      *flags;
224   
225   /* Note: the result is normalized, that is, it contains / as the separator */
226   folderName = [_url path];
227   if ([folderName hasPrefix:@"/"]) 
228     folderName = [folderName substringFromIndex:1];
229   
230   result = [_result valueForKey:@"list"];
231   
232   /* Cyrus already tells us whether we need to check for children */
233   flags = [result objectForKey:folderName];
234   if ([flags containsObject:@"hasnochildren"])
235     return nil;
236   
237   names = [self _getDirectChildren:[result allKeys] folderName:folderName];
238 #if 0
239   [self debugWithFormat:@"subfolders of %@: %@", folderName, 
240         [names componentsJoinedByString:@","]];
241 #endif
242   return names;
243 }
244
245 - (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
246   // TODO: add caching
247   SOGoMailConnectionEntry *entry;
248   NGImap4Client *client;
249   NSDictionary  *result;
250   
251   /* check cache */
252   
253   if ((entry = [self entryForURL:_url]) != nil) {
254     if ([entry isValidPassword:_pwd]) {
255       NSDictionary *allFolders;
256       
257       [self debugWithFormat:@"valid password, reusing folder cache .."];
258       if ((allFolders = [entry cachedHierarchyResults]) != nil)
259         return [self extractSubfoldersForURL:_url fromResultSet:allFolders];
260       
261       [self debugWithFormat:@"  no folders cached yet .."];
262     }
263     
264     /* different password, password could have changed! */
265     entry = nil;
266   }
267   else
268     [self debugWithFormat:@"no connection cached yet for url: %@", _url];
269
270   /* get client */
271   
272   client = [entry isValidPassword:_pwd]
273     ? [entry client]
274     : [self imap4ClientForURL:_url password:_pwd];
275   if (client == nil)
276     return nil;
277   
278   /* fetch _all_ folders */
279   
280   result = [client list:@"INBOX" pattern:@"*"];
281   if (![[result valueForKey:@"result"] boolValue]) {
282     [self logWithFormat:@"ERROR: listing of folder failed!"];
283     return nil;
284   }
285   
286   /* cache results */
287   
288   if ([result isNotNull]) {
289     if (entry == nil) /* required in case the entry was not setup */
290       entry = [self entryForURL:_url];
291     
292     [entry cacheHierarchyResults:result];
293     [self debugWithFormat:@"cached results in entry %@: %@", entry, result];
294   }
295   
296   /* extract list */
297   
298   return [self extractSubfoldersForURL:_url fromResultSet:result];
299 }
300
301 /* debugging */
302
303 - (BOOL)isDebuggingEnabled {
304   return debugOn;
305 }
306
307 @end /* SOGoMailManager */
308
309 @implementation SOGoMailConnectionEntry
310
311 - (id)initWithClient:(NGImap4Client *)_client password:(NSString *)_pwd {
312   if (_client == nil || _pwd == nil) {
313     [self release];
314     return nil;
315   }
316   
317   if ((self = [super init])) {
318     self->client   = [_client retain];
319     self->password = [_pwd    copy];
320     
321     self->creationTime = [[NSDate alloc] init];
322   }
323   return self;
324 }
325 - (id)init {
326   return [self initWithClient:nil password:nil];
327 }
328
329 - (void)dealloc {
330   [self->creationTime release];
331   [self->subfolders   release];
332   [self->password     release];
333   [self->client       release];
334   [super dealloc];
335 }
336
337 /* accessors */
338
339 - (NGImap4Client *)client {
340   return self->client;
341 }
342 - (BOOL)isValidPassword:(NSString *)_pwd {
343   return [self->password isEqualToString:_pwd];
344 }
345
346 - (NSDate *)creationTime {
347   return self->creationTime;
348 }
349
350 - (void)cacheHierarchyResults:(NSDictionary *)_hierarchy {
351   ASSIGNCOPY(self->subfolders, _hierarchy);
352 }
353 - (NSDictionary *)cachedHierarchyResults {
354   return self->subfolders;
355 }
356
357 @end /* SOGoMailConnectionEntry */