]> err.no Git - sope/blob - sope-mime/NGImap4/NGImap4Context.m
357f125708a821767da8a148c28d4f626449e307
[sope] / sope-mime / NGImap4 / NGImap4Context.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE 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   SOPE 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 SOPE; 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 "NGImap4Context.h"
23 #include "NGImap4Client.h"
24 #include "NGImap4Folder.h"
25 #include "NGImap4ServerRoot.h"
26 #include "NGImap4Support.h"
27 #include "NGImap4Functions.h"
28 #include "imCommon.h"
29
30 @interface NGImap4Context(Private)
31 - (void)initializeSentFolder;
32 - (void)initializeTrashFolder;
33 - (void)initializeDraftsFolder;
34 - (void)initializeInboxFolder;
35 - (void)initializeServerRoot;
36
37 - (void)_setSortEncoding:(NSString *)_str;
38 - (void)_setSubscribeFolderFailed:(BOOL)_b;
39 - (void)_setShowOnlySubscribedInRoot:(BOOL)_b;
40 - (void)_setShowOnlySubscribedInSubFolders:(BOOL)_b;
41 @end
42
43 @implementation NGImap4Context
44
45 static id  DefaultForSortEncoding                   = nil;
46 static id  DefaultForSubscribeFailed                = nil;
47 static id  DefaultForShowOnlySubscribedInRoot       = nil;
48 static id  DefaultForShowOnlySubscribedInSubFolders = nil;
49 static int ImapLogEnabled                           = -1;
50
51 + (void)initialize {
52   NSUserDefaults *ud;
53   static BOOL didInit = NO;
54   if (didInit) return;
55   didInit = YES;
56
57   ud = [NSUserDefaults standardUserDefaults];
58
59   DefaultForSortEncoding    = [[ud stringForKey:@"ImapSortEncoding"] copy];
60   DefaultForSubscribeFailed = 
61     [[ud stringForKey:@"ImapSubscribedCouldFailed"] copy];
62   DefaultForShowOnlySubscribedInSubFolders =
63     [[ud stringForKey:@"ShowOnlySubscribedInSubFolders"] copy];
64   DefaultForShowOnlySubscribedInRoot =
65     [[ud stringForKey:@"ShowOnlySubscribedInRoot"] copy];
66   
67   ImapLogEnabled = [ud boolForKey:@"ImapLogEnabled"]?1:0;
68 }
69
70 + (id)imap4ContextWithURL:(id)_url {
71   if (_url == nil)
72     return nil;
73   if (![_url isKindOfClass:[NSURL class]]) 
74     _url = [NSURL URLWithString:[_url stringValue]];
75   
76   return [[(NGImap4Context *)[self alloc] initWithURL:_url] autorelease];
77 }
78 + (id)imap4ContextWithConnectionDictionary:(NSDictionary *)_connection {
79   return [[[self alloc] initWithConnectionDictionary:_connection] autorelease];
80 }
81
82 - (id)initWithConnectionDictionary:(NSDictionary *)_connection {
83   if ((self = [super init])) {
84     self->connectionDictionary = [_connection copy];
85     self->folderForRefresh = [[NSMutableArray alloc] initWithCapacity:512];
86     self->syncMode = NO;
87
88     self->subscribeFolderFailed = (DefaultForSubscribeFailed)
89       ? [DefaultForSubscribeFailed boolValue]?1:0
90       : -1;
91
92     self->showOnlySubscribedInRoot = (DefaultForShowOnlySubscribedInRoot)
93       ? [DefaultForShowOnlySubscribedInRoot boolValue]?1:0
94       : -1;
95
96     self->showOnlySubscribedInSubFolders =
97       (DefaultForShowOnlySubscribedInSubFolders)
98       ? [DefaultForShowOnlySubscribedInSubFolders boolValue]?1:0
99       : -1;
100
101     self->sortEncoding = (DefaultForSortEncoding)
102       ? [DefaultForSortEncoding retain]
103       : nil;
104
105   }
106   return self;
107 }
108 - (id)initWithNSURL:(NSURL *)_url {
109   NSMutableDictionary *md;
110   id                  tmp;
111   
112   if (_url == nil) {
113     [self release];
114     return nil;
115   }
116   
117   md = [NSMutableDictionary dictionaryWithCapacity:4];
118   if ((tmp = [_url host]))     [md setObject:tmp forKey:@"host"];
119   if ((tmp = [_url port]))     [md setObject:tmp forKey:@"port"];
120   if ((tmp = [_url user]))     [md setObject:tmp forKey:@"login"];
121   if ((tmp = [_url password])) [md setObject:tmp forKey:@"passwd"];
122   
123   if ([[_url scheme] isEqualToString:@"imaps"])
124     [md setObject:[NSNumber numberWithBool:YES] forKey:@"SSL"];
125   
126   return [self initWithConnectionDictionary:md];
127 }
128 - (id)initWithURL:(id)_url {
129   if ((_url != nil) && ![_url isKindOfClass:[NSURL class]])
130     _url = [NSURL URLWithString:[_url stringValue]];
131   
132   return [self initWithNSURL:_url];
133 }
134
135 - (void)dealloc {
136   [self->url release];
137   [self->client removeFromResponseNotification:self];
138   [self->connectionDictionary release];
139   [self->client           release];
140   [self->folderForRefresh release];
141   [self->serverName       release];       
142   [self->serverKind       release];       
143   [self->serverVersion    release];    
144   [self->serverSubVersion release]; 
145   [self->serverTag        release];        
146   [self->lastException    release];
147
148   self->selectedFolder = nil; /* not retained */  
149   self->trashFolder    = nil; /* not retained */  
150   self->draftsFolder   = nil; /* not retained */  
151   self->sentFolder     = nil; /* not retained */
152   self->inboxFolder    = nil; /* not retained */
153   self->serverRoot     = nil; /* not retained */
154
155   [self->capability   release];
156   [self->sortEncoding release];
157   
158   [super dealloc];
159 }
160
161 /* accessors */
162
163 - (NSException *)lastException {
164   return self->lastException;
165 }
166
167 - (void)setLastException:(NSException *)_exception {
168   ASSIGN(self->lastException, _exception);
169 }
170
171 - (void)resetLastException {
172   [self->lastException release]; self->lastException = nil;
173 }
174
175 - (NGImap4Client *)client {
176   if (self->client == nil)
177     [self openConnection];
178   return self->client;
179 }
180
181 - (EOGlobalID *)serverGlobalID {
182   if (self->client == nil) {
183     // TODO: construct global-id using connectionDictionary!
184     [self logWithFormat:@"WARNING: could not construct GID (client not set!)"];
185     return nil;
186   }
187   return [self->client serverGlobalID];
188 }
189
190 - (NSURL *)url {
191   NSString *scheme;
192   
193   if (self->url)
194     return self->url;
195   
196   scheme = [[self->connectionDictionary objectForKey:@"SSL"] boolValue]
197     ? @"imaps" : @"imap";
198   
199   self->url = [[NSURL alloc]
200                 initWithScheme:scheme
201                 host:[self->connectionDictionary objectForKey:@"host"]
202                 path:nil];
203   return self->url;
204 }
205
206 /* folder tracking */
207
208 - (BOOL)isSelectedFolder:(NGImap4Folder *)_folder {
209   return [self->selectedFolder isEqual:_folder];
210 }
211
212 - (void)setSelectedFolder:(NGImap4Folder *)_folder {
213   self->selectedFolder = _folder;
214 }
215
216 - (BOOL)registerAsSelectedFolder:(NGImap4Folder *)_folder {
217   id tmp;
218   
219   if (self->selectedFolder == _folder)
220     return YES;
221   
222   if ([_folder noselect])
223     return NO;
224   
225   tmp = self->selectedFolder;
226   self->selectedFolder = _folder;
227   if (![_folder selectImmediately:YES]) {
228     self->selectedFolder = tmp;
229     return NO;
230   }
231
232   return YES;
233 }
234
235 - (BOOL)removeSelectedFolder:(NGImap4Folder *)_folder {
236   if (self->selectedFolder == _folder)
237     self->selectedFolder = nil;
238   return YES;
239 }
240
241 - (BOOL)openConnection {
242   NSString *login;
243   NSString *passwd;
244   NSString *host;
245
246   login  = [self->connectionDictionary objectForKey:@"login"];
247   passwd = [self->connectionDictionary objectForKey:@"passwd"];
248   host   = [self->connectionDictionary objectForKey:@"host"];
249
250   [self resetSpecialFolders];
251
252   if ((login == nil) || (passwd == nil) || (host == nil)) {
253     id exc;
254     
255     exc = [[NGImap4ConnectionException alloc] 
256             initWithFormat:
257               @"missing login, passwd or host in connection-dictionary <%@>",
258               self->connectionDictionary];
259     ASSIGN(self->lastException, exc);
260     [exc release];
261     return NO;
262   }
263   if (self->client == nil) {
264     self->client = [[NGImap4Client alloc] initWithHost:host];
265     [self->client registerForResponseNotification:self];
266     [self->client setContext:self];
267   }
268   if (![[self->client isConnected] boolValue]) {
269     NSDictionary *res;
270     
271     [self resetLastException];
272     
273     res = [self->client openConnection];
274     if (!_checkResult(self, res, __PRETTY_FUNCTION__))
275       return NO;
276     
277     [self->serverName       release]; self->serverName       = nil;
278     [self->serverKind       release]; self->serverKind       = nil;
279     [self->serverVersion    release]; self->serverVersion    = nil;
280     [self->serverSubVersion release]; self->serverSubVersion = nil;
281     [self->serverTag        release]; self->serverTag        = nil;
282
283     self->serverName = [[res objectForKey:@"server"]     retain];
284     self->serverKind = [[res objectForKey:@"serverKind"] retain];
285
286     if (ImapLogEnabled) {
287       [self logWithFormat:@"Server greeting: <%@> parse serverkind: <%@>",
288             self->serverName, self->serverKind];
289     }
290     
291     // TODO: move capability to specialized object!
292     //   Note: capability of the IMAP4 server should be always configurable
293     //         using defaults because the identification might be broken for
294     //         security reasons
295     
296     if (self->serverKind != nil) {
297       self->serverVersion    = [[res objectForKey:@"version"]    retain];
298       self->serverSubVersion = [[res objectForKey:@"subversion"] retain];
299       self->serverTag        = [[res objectForKey:@"tag"]        retain];
300     }
301     if ([self->serverKind isEqual:@"courier"]) {
302       [self _setSortEncoding:@"US-ASCII"];
303       [self _setSubscribeFolderFailed:NO];
304       [self _setShowOnlySubscribedInRoot:0];
305       [self _setShowOnlySubscribedInSubFolders:0];
306     }
307     else if ([self->serverKind isEqual:@"washington"]) {
308       [self _setSortEncoding:@"UTF-8"];
309       [self _setSubscribeFolderFailed:YES];
310       [self _setShowOnlySubscribedInRoot:1];
311       [self _setShowOnlySubscribedInSubFolders:1];
312     }
313     else if ([self->serverKind isEqual:@"cyrus"]) {
314       [self _setSortEncoding:@"UTF-8"];
315       [self _setSubscribeFolderFailed:YES];
316       [self _setShowOnlySubscribedInRoot:0];
317       [self _setShowOnlySubscribedInSubFolders:0];
318     }
319     if (ImapLogEnabled)
320       [self logWithFormat:@"sortEncoding %@ subscribeFolderFailed %@ "
321             @"showOnlySubscribedInSubFolders %@ showOnlySubscribedInRoot %@",
322             [self sortEncoding],
323             [self subscribeFolderFailed]         ? @"YES" : @"NO",
324             [self showOnlySubscribedInSubFolders]? @"YES" : @"NO",
325             [self showOnlySubscribedInRoot]      ? @"YES" : @"NO"];
326       
327     [self resetLastException];
328     res = [self->client login:login password:passwd];
329     if (!_checkResult(self, res, __PRETTY_FUNCTION__))
330       return NO;
331   }
332   {
333     NSDictionary *res;
334
335     [self->capability release]; self->capability = nil;
336     res = [self->client capability];
337
338     if (!_checkResult(self, res, __PRETTY_FUNCTION__))
339       return NO;
340
341     self->capability = [[res objectForKey:@"capability"] retain];
342   }
343
344   self->canSort  = -1;
345   self->canQuota = -1;
346   
347   return YES;
348 }
349
350 - (void)_setSortEncoding:(NSString *)_str {
351   if (!DefaultForSortEncoding)
352     ASSIGN(self->sortEncoding, _str);
353 }
354
355 - (void)_setSubscribeFolderFailed:(BOOL)_b {
356   if (!DefaultForSubscribeFailed)
357     self->subscribeFolderFailed = _b?1:0;
358 }
359
360 - (void)_setShowOnlySubscribedInRoot:(BOOL)_b {
361   if (!DefaultForShowOnlySubscribedInRoot)
362     self->showOnlySubscribedInRoot = _b?1:0;
363 }
364
365 - (void)_setShowOnlySubscribedInSubFolders:(BOOL)_b {
366   if (!DefaultForShowOnlySubscribedInSubFolders)
367     self->showOnlySubscribedInSubFolders = _b?1:0;
368 }
369
370 - (void)setSortEncoding:(NSString *)_str {
371   ASSIGN(self->sortEncoding, _str);
372 }
373
374 - (void)setSubscribeFolderFailed:(BOOL)_b {
375   self->subscribeFolderFailed = _b?1:0;
376 }
377
378 - (void)setShowOnlySubscribedInRoot:(BOOL)_b {
379   self->showOnlySubscribedInRoot = _b?1:0;
380 }
381
382 - (void)setShowOnlySubscribedInSubFolders:(BOOL)_b {
383   self->showOnlySubscribedInSubFolders = _b?1:0;
384 }
385
386 - (BOOL)closeConnection {
387   [self->client closeConnection];
388   [self resetSpecialFolders];
389   [self->capability release]; self->capability = nil;
390   self->canSort = -1;
391   self->canQuota = -1;
392
393   
394   return YES;
395 }
396
397 /*"
398 **  NGImap4ResponseReceiver protocol
399 **  If the NGImap4Context receive a response-notification it
400 **  updates the selected folder
401 "*/
402
403 - (void)responseNotificationFrom:(NGImap4Client *)_client
404    response:(NSDictionary *)_dict
405 {
406   if (![[_dict objectForKey:@"result"] boolValue]) {
407     id       exc;
408     NSString *str;
409     
410     if ((str = [_dict objectForKey:@"reason"]) == nil)
411       str = @"Response failed";
412     
413     exc = [[NGImap4ResponseException alloc] 
414       initWithName:@"NGImap4ResponseException" reason:str userInfo:_dict];
415     ASSIGN(self->lastException, exc);
416     return;
417   }
418
419   if (self->selectedFolder)
420     [self->selectedFolder processResponse:_dict];
421 }
422
423 - (id)trashFolder {
424   if (self->trashFolder == nil)
425     [self initializeTrashFolder];
426   return self->trashFolder;
427 }
428 - (void)setTrashFolder:(NGImap4Folder *)_folder {
429   self->trashFolder = _folder;
430 }
431
432 - (id)sentFolder {
433   if (self->sentFolder == nil)
434     [self initializeSentFolder];
435   return self->sentFolder;
436 }
437 - (void)setSentFolder:(NGImap4Folder *)_folder {
438   self->sentFolder = _folder;
439 }
440
441 - (id)draftsFolder {
442   if (self->draftsFolder == nil)
443     [self initializeDraftsFolder];
444   return self->draftsFolder;
445 }
446 - (void)setDraftsFolder:(NGImap4Folder *)_folder {
447   self->draftsFolder = _folder;
448 }
449
450 - (id)inboxFolder {
451   if (self->inboxFolder == nil)
452     [self initializeInboxFolder];
453   return self->inboxFolder;
454 }
455
456 - (id)serverRoot {
457   if (self->serverRoot == nil)
458     [self initializeServerRoot];
459   return self->serverRoot;
460 }
461
462 - (void)initializeServerRoot {
463   if (self->serverRoot == nil) {
464     /*
465       Note: serverRoot is not retained by NGImap4Context to avoid a
466       retain cycle. This is why the object is autoreleased immediatly
467       after creation (the usercode uses -serverRoot to access the result).
468     */
469     [self resetSpecialFolders];
470     self->serverRoot = [[NGImap4ServerRoot alloc]
471                                            initServerRootWithContext:self];
472     self->serverRoot = [self->serverRoot autorelease];
473   }
474 }
475
476 - (void)_checkFolder:(id)_folder folderName:(NSString *)_name
477 {
478   NSDictionary *res;
479   NSArray      *list;
480
481   [self resetLastException];
482   res = [self->client list:@"" pattern:_name];
483
484   if (![[res objectForKey:@"result"] boolValue])
485     return;
486
487   list = [res objectForKey:@"list"];
488
489   if ([list count]) { /* folder exist but is not subscribed */
490     [self->client subscribe:_name];
491   }
492   else { /* try to create folder */
493     [_folder createSubFolderWithName:[_name lastPathComponent]];
494   }
495   [_folder resetSubFolders];
496   [self->lastException release]; self->lastException = nil;
497 }
498
499 - (NGImap4Folder *)_getFolderWithName:(NSString *)_name {
500   NSEnumerator  *enumerator;
501   NGImap4Folder *folder;
502
503   if (self->serverRoot == nil)
504     [self initializeServerRoot];
505
506   enumerator = [[self->serverRoot subFolders] objectEnumerator];
507   while ((folder = [enumerator nextObject])) {
508     NSString *name;
509
510     name = [[folder name] lowercaseString];
511
512     if ([name isEqualToString:[_name lowercaseString]]) {
513       return folder;
514     }
515   }
516   if ([[_name lowercaseString] isEqual:@"inbox"]) {
517     [self resetLastException];
518     [self->client subscribe:_name];
519     if (self->lastException != nil) {
520       [self->serverRoot createSubFolderWithName:_name];
521       [self->lastException release]; self->lastException = nil;
522     }
523     [self resetSpecialFolders];
524   }
525   else {
526     if ([[self inboxFolder] noinferiors]) {
527       /* try to create Sent/Trash/Drafts in root */
528       [self _checkFolder:self->serverRoot folderName:_name];
529     }
530     else {
531       /* take a look in inbox */
532       NGImap4Folder *f;
533       NSString      *absoluteName;
534
535       f      = [self inboxFolder];
536       folder = [f subFolderWithName:[_name lowercaseString]
537                   caseInsensitive:YES];
538       if (folder != nil)
539         return folder;
540       
541       absoluteName = [[f absoluteName] stringByAppendingPathComponent:_name];
542       
543       [self _checkFolder:f folderName:absoluteName];
544     }
545   }
546   return nil;
547 }
548
549 - (NSString *)sentFolderName {
550   static NSString *SentFolderName = nil;
551
552   if (SentFolderName == nil) {
553     SentFolderName = [[[NSUserDefaults standardUserDefaults]
554                                       stringForKey:@"ImapSentFolderName"]
555                                        retain];
556
557     if (!SentFolderName)
558       SentFolderName = @"Sent";
559   }
560   return SentFolderName;
561 }
562
563 - (NSString *)trashFolderName {
564   static NSString *TrashFolderName = nil;
565
566   if (TrashFolderName == nil) {
567     TrashFolderName = [[[NSUserDefaults standardUserDefaults]
568                                       stringForKey:@"ImapTrashFolderName"]
569                                        retain];
570
571     if (!TrashFolderName)
572       TrashFolderName = @"Trash";
573   }
574   return TrashFolderName;
575 }
576
577 - (NSString *)draftsFolderName {
578   static NSString *DraftsFolderName = nil;
579
580   if (DraftsFolderName == nil) {
581     DraftsFolderName = [[[NSUserDefaults standardUserDefaults]
582                                       stringForKey:@"ImapDraftsFolderName"]
583                                        retain];
584
585     if (!DraftsFolderName)
586       DraftsFolderName = @"Drafts";
587   }
588   return DraftsFolderName;
589 }
590
591 - (void)initializeSentFolder {
592   if ((self->sentFolder = [self _getFolderWithName:
593                                 [self sentFolderName]]) == nil)
594     self->sentFolder = [self _getFolderWithName:
595                              [self sentFolderName]];
596   if (self->sentFolder == nil)
597     NSLog(@"WARNING[%s]: Couldn't find/create sentFolder", __PRETTY_FUNCTION__);
598 }
599
600 - (void)initializeTrashFolder {
601   if ((self->trashFolder = [self _getFolderWithName:
602                                  [self trashFolderName]]) == nil)
603     self->trashFolder = [self _getFolderWithName:[self trashFolderName]];
604   if (self->trashFolder == nil)
605     NSLog(@"WARNING[%s]: Couldn't find/create trashFolder", __PRETTY_FUNCTION__);
606 }
607
608 - (void)initializeDraftsFolder {
609   if ((self->draftsFolder = [self _getFolderWithName:
610                                   [self draftsFolderName]]) == nil)
611     self->draftsFolder = [self _getFolderWithName:
612                                [self draftsFolderName]];
613   if (self->draftsFolder == nil)
614     NSLog(@"WARNING[%s]: Couldn't find/create draftsFolder", __PRETTY_FUNCTION__);
615 }
616
617 - (void)initializeInboxFolder {
618   if ((self->inboxFolder = [self _getFolderWithName:@"Inbox"]) == nil)
619     self->inboxFolder = [self _getFolderWithName:@"Inbox"];
620   
621   if (self->inboxFolder == nil)
622     NSLog(@"WARNING[%s]: Couldn't find/create inbox", __PRETTY_FUNCTION__);
623 }
624
625 - (NGImap4Folder *)folderWithName:(NSString *)_name {
626   return [self folderWithName:_name caseInsensitive:NO];
627 }
628
629 - (NGImap4Folder *)folderWithName:(NSString *)_name
630   caseInsensitive:(BOOL)_caseIn 
631 {
632   NSEnumerator  *enumerator;
633   id            obj;
634   NGImap4Folder *f;
635
636   [self resetLastException];
637   enumerator = [[_name componentsSeparatedByString:@"/"] objectEnumerator];
638   f          = [self serverRoot];
639   
640   while ((obj = [enumerator nextObject])) {
641     if ([obj length] > 0)
642       f = [f subFolderWithName:obj caseInsensitive:_caseIn];
643   }
644   return self->lastException ? nil : f;
645 }
646
647 - (BOOL)createFolderWithPath:(NSString *)_name {
648   NSEnumerator      *enumerator;
649   id<NGImap4Folder> f1, f2; 
650   NSString          *name;
651
652   [self resetLastException];
653   
654   enumerator = [[_name componentsSeparatedByString:@"/"] objectEnumerator];
655   f1         = [self serverRoot];
656   f2         = nil;
657   while ((name = [enumerator nextObject])) {
658     if ((f2 = [f1 subFolderWithName:name caseInsensitive:YES]) == nil)
659       break;
660     f1 = f2;
661   }
662   if (name != nil) {
663     do {
664       if (![f1 createSubFolderWithName:name])
665         break;
666       f1 = [f1 subFolderWithName:name caseInsensitive:YES];
667     } while ((name = [enumerator nextObject]));
668   }
669   return self->lastException ? NO : YES;
670 }
671
672 - (void)resetSpecialFolders {
673   self->sentFolder   = nil;
674   self->trashFolder  = nil;
675   self->draftsFolder = nil;
676   self->inboxFolder  = nil;
677   self->serverRoot   = nil;
678 }
679
680 - (NSArray *)newMessages {
681   NSEnumerator   *enumerator;
682   NGImap4Folder  *f;
683   NSMutableArray *result;
684   EOQualifier    *qual;
685
686   [self resetLastException];
687   
688   qual   = [EOQualifier qualifierWithQualifierFormat:@"flags = \"recent\""];
689   result = [NSMutableArray array];
690   
691   [self->inboxFolder status];
692   if ([self->inboxFolder hasNewMessagesSearchRecursiv:NO]) {
693     NSArray *array;
694
695     array = [self->inboxFolder messagesForQualifier:qual];
696     if (array != nil)
697       [result addObjectsFromArray:array];
698   }
699   enumerator = [self->folderForRefresh objectEnumerator];  
700   while ((f = [enumerator nextObject])) {
701     [f status];
702     if ([f hasNewMessagesSearchRecursiv:NO]) {
703     NSArray *array;
704
705     array = [self->inboxFolder messagesForQualifier:qual];
706     if (array != nil)
707       [result addObjectsFromArray:array];
708     }
709   }
710   return self->lastException ? nil : result;
711 }
712
713 - (BOOL)hasNewMessages {
714   NSEnumerator  *enumerator;
715   NGImap4Folder *f;
716   BOOL          result;
717
718   [self resetLastException];
719   
720   [self->inboxFolder status];
721   if ([self->inboxFolder hasNewMessagesSearchRecursiv:NO])
722     return YES;
723
724   result     = NO;
725   enumerator = [self->folderForRefresh objectEnumerator];
726   
727   while ((f = [enumerator nextObject])) {
728     [f status];
729     if ([f hasNewMessagesSearchRecursiv:NO]) {
730       result = YES;
731       break;
732     }
733   }
734   return self->lastException ? NO : result;
735 }
736
737 - (NSString *)host {
738   return [self->connectionDictionary objectForKey:@"host"];
739 }
740 - (NSString *)login {
741   return [self->connectionDictionary objectForKey:@"login"];
742 }
743
744 - (BOOL)registerForRefresh:(NGImap4Folder *)_folder {
745   [self->folderForRefresh addObject:_folder];
746   return YES;
747 }
748
749 - (BOOL)removeFromRefresh:(NGImap4Folder *)_folder {
750   [self->folderForRefresh removeObject:_folder];
751   return YES;
752 }
753
754 - (BOOL)removeAllFromRefresh {
755   [self->folderForRefresh removeAllObjects];
756   return YES;
757 }
758
759 - (BOOL)refreshFolder {
760   // TODO: explain
761   //       this runs status on each folder and status triggers notifications?
762   NSEnumerator  *enumerator;
763   NGImap4Folder *f;
764   BOOL          refreshInbox = NO;
765
766   if ([self lastException] != nil)
767     return NO;
768   
769   enumerator = [self->folderForRefresh objectEnumerator];
770
771   [self resetLastException];
772
773   while ((f = [enumerator nextObject])) {
774     if ([f isEqual:self->inboxFolder])
775       refreshInbox = YES;
776     
777     [f status];
778   }
779   
780   if (!refreshInbox)
781     [self->inboxFolder status];
782     
783   return self->lastException ? NO : YES;
784 }
785
786 - (id)serverName {
787   return self->serverName;
788 }
789 - (id)serverKind {
790   return self->serverKind;
791 }
792 - (id)serverVersion {
793   return self->serverVersion;
794 }
795 - (id)serverSubVersion {
796   return self->serverSubVersion;
797 }
798 - (id)serverTag {
799   return self->serverTag;
800 }
801
802 /* synchronize */
803
804 - (void)resetSync {
805   if (self->syncMode) 
806     [self->serverRoot resetSync];
807   else
808     [self logWithFormat:@"WARNING: resetSync has no effect if syncMode == NO"];
809 }
810
811 - (BOOL)isInSyncMode {
812   return self->syncMode;
813 }
814
815 - (void)enterSyncMode {
816   self->syncMode = YES;
817   [self resetSync];
818 }
819
820 - (void)leaveSyncMode {
821   self->syncMode = NO;
822 }
823
824 - (BOOL)showOnlySubscribedInRoot {
825   if (self->showOnlySubscribedInRoot == -1)
826     return NO;
827   
828   return (self->showOnlySubscribedInRoot == 1) ? YES : NO;
829 }
830
831 - (BOOL)showOnlySubscribedInSubFolders {
832   if (self->showOnlySubscribedInSubFolders == -1)
833     return NO;
834   
835   return (self->showOnlySubscribedInSubFolders == 1) ? YES : NO;
836 }
837
838 - (BOOL)subscribeFolderFailed {
839   if (self->subscribeFolderFailed == -1)
840     return YES;
841   
842   return (self->subscribeFolderFailed == 1) ? YES : NO;
843 }
844
845 - (NSString *)sortEncoding {
846   if (self->sortEncoding == nil)
847     self->sortEncoding = @"UTF-8";
848   
849   return self->sortEncoding;
850 }
851
852 /* Capability */
853
854 - (BOOL)canSort {
855   if (self->capability == nil) {
856     if (![self openConnection])
857       return NO;
858   }
859   if (self->canSort == -1) {
860     self->canSort =
861       ([self->capability containsObject:@"sort"])? 1 : 0;
862   }
863   return self->canSort;
864 }
865
866 - (BOOL)canQuota {
867   if (self->capability == nil) {
868     if (![self openConnection])
869       return NO;
870   }
871   if (self->canQuota == -1) {
872     self->canQuota =
873       ([self->capability containsObject:@"quota"])? 1 : 0;
874   }
875   return self->canQuota;
876 }
877
878 /* URL based factory */
879
880 + (id)messageWithURL:(id)_url {
881   NGImap4Context *ctx;
882   
883   if (_url == nil) 
884     return nil;
885   if (![_url isKindOfClass:[NSURL class]]) 
886     _url = [NSURL URLWithString:[_url stringValue]];
887   
888   if ((ctx = [self imap4ContextWithURL:_url]) == nil) {
889     NSLog(@"WARNING(%s): got no IMAP4 context for URL: %@", 
890           __PRETTY_FUNCTION__, _url);
891     return nil;
892   }
893   return [ctx messageWithURL:_url];
894 }
895 - (id)folderWithURL:(id)_url {
896   NSString *path, *folderPath;
897   
898   if (_url != nil && ![_url isKindOfClass:[NSURL class]]) 
899     _url = [NSURL URLWithString:[_url stringValue]];
900   if (_url == nil) 
901     return nil;
902   
903   path       = [_url path];
904   folderPath = [path stringByDeletingLastPathComponent];
905   
906   return [self folderWithName:folderPath];
907 }
908 - (id)messageWithURL:(id)_url {
909   NSString      *path, *folderPath;
910   NGImap4Folder *f;
911   unsigned      messageID;
912   
913   if (_url != nil && ![_url isKindOfClass:[NSURL class]]) 
914     _url = [NSURL URLWithString:[_url stringValue]];
915   if (_url == nil) 
916     return nil;
917   
918   path       = [_url path];
919   folderPath = [path stringByDeletingLastPathComponent];
920   messageID  = [[path lastPathComponent] intValue];
921   
922   if ((f = [self folderWithName:folderPath]) == nil) {
923     [self logWithFormat:@"WARNING(%s): missing folder for URL: '%@'",
924           __PRETTY_FUNCTION__, _url];
925     return nil;
926   }
927   return [f messageWithUid:messageID];
928 }
929
930 /* description */
931
932 - (NSString *)description {
933   NSMutableString *ms;
934   NSString *tmp;
935
936   ms = [NSMutableString stringWithCapacity:64];
937
938   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
939
940   if ((tmp = [self host]))
941     [ms appendFormat:@" host=%@", tmp];
942   if ((tmp = [self login]))
943     [ms appendFormat:@" login=%@", tmp];
944   
945   [ms appendFormat:@" server='%@'/%@/%@.%@/%@",
946         [self serverName],
947         [self serverKind],
948         [self serverVersion],
949         [self serverSubVersion],
950         [self serverTag]];
951   
952   if (self->syncMode)
953     [ms appendString:@" syncmode"];
954   
955   [ms appendString:@">"];
956
957   return ms;
958 }
959
960 @end /* NGImap4Context(Capability) */