2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
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
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.
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
22 #include "SOGoMailManager.h"
23 #include "SOGoMailConnectionEntry.h"
27 Could check read-write state:
28 dict = [[self->context client] select:[self absoluteName]];
30 [[dict objectForKey:@"access"] isEqualToString:@"READ-WRITE"]
31 ? NoNumber : YesNumber;
33 TODO: to implement copy, use "uid copy" instead of "copy" as used by
37 @implementation SOGoMailManager
39 static BOOL debugOn = NO;
40 static BOOL debugCache = NO;
41 static BOOL debugKeys = NO;
42 static BOOL poolingOff = NO;
43 static NSTimeInterval PoolScanInterval = 5 * 60 /* every five minutes */;
46 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
48 debugOn = [ud boolForKey:@"SOGoEnableIMAP4Debug"];
49 debugCache = [ud boolForKey:@"SOGoEnableIMAP4CacheDebug"];
50 poolingOff = [ud boolForKey:@"SOGoDisableIMAP4Pooling"];
52 if (debugOn) NSLog(@"Note: SOGoEnableIMAP4Debug is enabled!");
53 if (poolingOff) NSLog(@"WARNING: IMAP4 connection pooling is disabled!");
56 + (id)defaultMailManager {
57 static SOGoMailManager *manager = nil; // THREAD
59 manager = [[self alloc] init];
64 if ((self = [super init])) {
66 self->urlToEntry = [[NSMutableDictionary alloc] initWithCapacity:256];
69 self->gcTimer = [[NSTimer scheduledTimerWithTimeInterval:
71 target:self selector:@selector(_garbageCollect:)
72 userInfo:nil repeats:YES] retain];
78 if (self->gcTimer) [self->gcTimer invalidate];
79 [self->gcTimer release];
81 [self->urlToEntry release];
87 - (id)cacheKeyForURL:(NSURL *)_url {
88 // protocol, user, host, port
89 return [NSString stringWithFormat:@"%@://%@@%@:%@",
90 [_url scheme], [_url user], [_url host], [_url port]];
93 - (SOGoMailConnectionEntry *)entryForURL:(NSURL *)_url {
97 return [self->urlToEntry objectForKey:[self cacheKeyForURL:_url]];
99 - (void)cacheEntry:(SOGoMailConnectionEntry *)_entry forURL:(NSURL *)_url {
100 if (_entry == nil) _entry = (id)[NSNull null];
101 [self->urlToEntry setObject:_entry forKey:[self cacheKeyForURL:_url]];
104 - (void)_garbageCollect:(NSTimer *)_timer {
105 // TODO: scan for old IMAP4 channels
106 [self debugWithFormat:@"should collect IMAP4 channels (%d active)",
107 [self->urlToEntry count]];
110 - (id)entryForURL:(NSURL *)_url password:(NSString *)_pwd {
113 a) not yet connected => create new entry and connect
114 b) connected, correct password => return cached entry
115 c) connected, different password => try to recreate entry
117 SOGoMailConnectionEntry *entry;
118 NGImap4Client *client;
122 if ((entry = [self entryForURL:_url]) != nil) {
123 if ([entry isValidPassword:_pwd]) {
125 [self logWithFormat:@"valid password, reusing cache entry ..."];
129 /* different password, password could have changed! */
131 [self logWithFormat:@"different password than cached entry: %@", _url];
135 [self debugWithFormat:@"no connection cached yet for url: %@", _url];
139 client = [entry isValidPassword:_pwd]
141 : [self imap4ClientForURL:_url password:_pwd];
146 /* sideeffect of -imap4ClientForURL:password: is to create a cache entry */
147 return [self entryForURL:_url];
150 - (NSException *)errorForMissingEntryAtURL:(NSURL *)_url {
152 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
153 reason:@"Did not find mail URL"];
158 - (NGImap4Client *)imap4ClientForURL:(NSURL *)_url password:(NSString *)_pwd {
159 // TODO: move to some global IMAP4 connection pool manager
160 SOGoMailConnectionEntry *entry;
161 NGImap4Client *client;
162 NSDictionary *result;
167 /* check connection pool */
169 if ((entry = [self entryForURL:_url]) != nil) {
170 if ([entry isValidPassword:_pwd]) {
171 [self debugWithFormat:@"reused IMAP4 connection for URL: %@", _url];
172 return [entry client];
175 /* different password, password could have changed! */
179 /* setup connection and attempt login */
181 if ((client = [NGImap4Client clientWithURL:_url]) == nil)
184 result = [client login:[_url user] password:_pwd];
185 if (![[result valueForKey:@"result"] boolValue]) {
186 [self errorWithFormat:
187 @"IMAP4 login failed:\n"
188 @" host=%@, user=%@, pwd=%s\n"
189 @" url=%@\n base=%@\n base-class=%@)\n"
191 [_url host], [_url user], [_pwd length] > 0 ? "yes" : "no",
192 [_url absoluteString],
194 NSStringFromClass([[_url baseURL] class]),
199 [self debugWithFormat:@"created new IMAP4 connection for URL: %@", _url];
201 /* cache connection in pool */
203 entry = [[SOGoMailConnectionEntry alloc] initWithClient:client
205 [self cacheEntry:entry forURL:_url];
206 [entry release]; entry = nil;
211 - (void)flushCachesForURL:(NSURL *)_url {
212 SOGoMailConnectionEntry *entry;
214 if ((entry = [self entryForURL:_url]) == nil) /* nothing cached */
217 [entry flushFolderHierarchyCache];
218 [entry flushMailCaches];
221 /* folder hierarchy */
223 - (NSArray *)subfoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
224 SOGoMailConnectionEntry *entry;
227 [self debugWithFormat:@"subfolders for URL: %@ ...",[_url absoluteString]];
229 /* check connection cache */
231 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
234 return [entry subfoldersForURL:_url];
237 - (NSArray *)allFoldersForURL:(NSURL *)_url password:(NSString *)_pwd {
238 SOGoMailConnectionEntry *entry;
241 [self debugWithFormat:@"folders for URL: %@ ...",[_url absoluteString]];
243 /* check connection cache */
245 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
248 return [entry allFoldersForURL:_url];
253 - (NSArray *)fetchUIDsInURL:(NSURL *)_url qualifier:(id)_qualifier
254 sortOrdering:(id)_so password:(NSString *)_pwd
257 sortOrdering can be an NSString, an EOSortOrdering or an array of EOS.
259 SOGoMailConnectionEntry *entry;
261 /* check connection cache */
262 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
265 return [entry fetchUIDsInURL:_url qualifier:_qualifier sortOrdering:_so];
268 - (NSArray *)fetchUIDs:(NSArray *)_uids inURL:(NSURL *)_url
269 parts:(NSArray *)_parts password:(NSString *)_pwd
271 // currently returns a dict?!
275 BODY.PEEK[<section>]<<partial>>
276 BODY [this is the bodystructure, supported]
277 BODYSTRUCTURE [not supported yet!]
278 ENVELOPE [this is a parsed header, but does not include type]
286 SOGoMailConnectionEntry *entry;
290 if ([_uids count] == 0)
291 return nil; // TODO: might break empty folders?! return a dict!
293 /* check connection cache */
294 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
297 return [entry fetchUIDs:_uids inURL:_url parts:_parts];
300 - (NSException *)expungeAtURL:(NSURL *)_url password:(NSString *)_pwd {
301 SOGoMailConnectionEntry *entry;
303 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
304 return [self errorForMissingEntryAtURL:_url];
306 return [entry expungeAtURL:_url];
309 - (id)fetchURL:(NSURL *)_url parts:(NSArray *)_parts password:(NSString *)_pwd{
310 SOGoMailConnectionEntry *entry;
312 if (![_url isNotNull]) return nil;
313 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
314 return [self errorForMissingEntryAtURL:_url];
316 return [entry fetchURL:_url parts:_parts];
319 - (NSData *)fetchContentOfBodyPart:(NSString *)_partId
320 atURL:(NSURL *)_url password:(NSString *)_pwd
322 SOGoMailConnectionEntry *entry;
324 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
325 return nil; // TODO: improve?
327 return [entry fetchContentOfBodyPart:_partId atURL:_url];
330 - (NSException *)addOrRemove:(BOOL)_flag flags:(id)_f
331 toURL:(NSURL *)_url password:(NSString *)_p
333 SOGoMailConnectionEntry *entry;
335 if ((entry = [self entryForURL:_url password:_p]) == nil)
336 return [self errorForMissingEntryAtURL:_url];
338 return [entry addOrRemove:_flag flags:_f toURL:_url];
340 - (NSException *)addFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p {
341 return [self addOrRemove:YES flags:_f toURL:_u password:_p];
343 - (NSException *)removeFlags:(id)_f toURL:(NSURL *)_u password:(NSString *)_p {
344 return [self addOrRemove:NO flags:_f toURL:_u password:_p];
347 - (NSException *)markURLDeleted:(NSURL *)_url password:(NSString *)_p {
348 return [self addOrRemove:YES flags:@"Deleted" toURL:_url password:_p];
351 - (NSException *)postData:(NSData *)_data flags:(id)_f
352 toFolderURL:(NSURL *)_url password:(NSString *)_p
354 SOGoMailConnectionEntry *entry;
356 if (![_url isNotNull]) return nil;
358 if ((entry = [self entryForURL:_url password:_p]) == nil)
359 return [self errorForMissingEntryAtURL:_url];
361 return [entry postData:_data flags:_f toFolderURL:_url];
364 - (NSException *)copyMailURL:(NSURL *)_srcurl toFolderURL:(NSURL *)_desturl
365 password:(NSString *)_pwd
367 SOGoMailConnectionEntry *entry;
369 /* check connection cache */
371 if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil)
372 return [self errorForMissingEntryAtURL:_srcurl];
374 /* check whether URLs are on different servers */
376 if ([self entryForURL:_desturl password:_pwd] != entry) {
377 // TODO: find a better error code
378 return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
379 reason:@"source and destination on different servers"];
382 return [entry copyMailURL:_srcurl toFolderURL:_desturl];
385 /* managing folders */
387 - (BOOL)isPermissionDeniedResult:(id)_result {
388 if ([[_result valueForKey:@"result"] intValue] != 0)
391 return [[_result valueForKey:@"reason"]
392 isEqualToString:@"Permission denied"];
395 - (BOOL)doesMailboxExistAtURL:(NSURL *)_url password:(NSString *)_pwd {
396 SOGoMailConnectionEntry *entry;
398 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
401 return [entry doesMailboxExistAtURL:_url];
404 - (id)infoForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
405 SOGoMailConnectionEntry *entry;
407 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
408 return [self errorForMissingEntryAtURL:_url];
410 return [entry infoForMailboxAtURL:_url];
413 - (NSException *)createMailbox:(NSString *)_mailbox atURL:(NSURL *)_url
414 password:(NSString *)_pwd
416 SOGoMailConnectionEntry *entry;
418 /* check connection cache */
419 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
420 return [self errorForMissingEntryAtURL:_url];
422 return [entry createMailbox:_mailbox atURL:_url];
425 - (NSException *)deleteMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
426 SOGoMailConnectionEntry *entry;
428 /* check connection cache */
430 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
431 return [self errorForMissingEntryAtURL:_url];
433 return [entry deleteMailboxAtURL:_url];
436 - (NSException *)moveMailboxAtURL:(NSURL *)_srcurl toURL:(NSURL *)_desturl
437 password:(NSString *)_pwd
439 SOGoMailConnectionEntry *entry;
441 /* check connection cache */
443 if ((entry = [self entryForURL:_srcurl password:_pwd]) == nil)
444 return [self errorForMissingEntryAtURL:_srcurl];
446 /* check whether URLs are on different servers */
448 if ([self entryForURL:_desturl password:_pwd] != entry) {
449 // TODO: find a better error code
450 return [NSException exceptionWithHTTPStatus:502 /* Bad Gateway */
451 reason:@"source and destination on different servers"];
454 return [entry moveMailboxAtURL:_srcurl toURL:_desturl];
457 - (NSDictionary *)aclForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
459 Returns a mapping of uid => permission strings, eg:
463 SOGoMailConnectionEntry *entry;
465 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
466 return (id)[self errorForMissingEntryAtURL:_url];
468 return [entry aclForMailboxAtURL:_url];
471 - (NSString *)myRightsForMailboxAtURL:(NSURL *)_url password:(NSString *)_pwd {
472 SOGoMailConnectionEntry *entry;
474 if ((entry = [self entryForURL:_url password:_pwd]) == nil)
475 return (id)[self errorForMissingEntryAtURL:_url];
477 return [entry myRightsForMailboxAtURL:_url];
480 /* bulk flag adding (eg used for empty/trash) */
482 - (NSException *)addFlags:(id)_f toAllMessagesInURL:(NSURL *)_url
483 password:(NSString *)_p
485 SOGoMailConnectionEntry *entry;
487 if (![_url isNotNull]) return nil;
488 if (![_f isNotNull]) return nil;
490 if ((entry = [self entryForURL:_url password:_p]) == nil)
491 return [self errorForMissingEntryAtURL:_url];
493 return [entry addFlags:_f toAllMessagesInURL:_url];
498 - (BOOL)isDebuggingEnabled {
502 @end /* SOGoMailManager */