2 Copyright (C) 2000-2005 SKYRIX Software AG
4 This file is part of SOPE.
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
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.
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
22 #include "NGImap4Folder.h"
23 #include "NGImap4Context.h"
24 #include "NGImap4Client.h"
25 #include "NGImap4Message.h"
26 #include "NGImap4Functions.h"
27 #include "NGImap4FolderGlobalID.h"
28 #include "NGImap4FolderMailRegistry.h"
29 #include "NGImap4FolderFlags.h"
32 @interface NGImap4Message(Private)
34 - (void)_setHeaders:(NGHashMap *)_headers
36 flags:(NSArray *)_flags;
39 - (void)setIsRead:(BOOL)_isRead;
41 @end /* NGImap4Message(Private) */
44 @interface NGImap4Context(Private)
45 - (void)setLastException:(NSException *)_exception;
46 - (void)setSelectedFolder:(NGImap4Folder *)_folder;
47 @end /* NGImap4Context(Private) */
49 @interface NGImap4Folder(Private)
52 - (void)_resetSubFolder;
54 - (NSArray *)initializeMessagesFrom:(unsigned)_from to:(unsigned)_to;
55 - (NSArray *)initializeMessages;
56 - (void)initializeSubFolders;
57 - (void)addSubFolder:(NGImap4Folder *)_folder;
58 - (BOOL)flag:(NSString*)_doof toMessages:(NSArray*)_msg add:(NSNumber*)_n;
59 - (BOOL)flagToAllMessages:(NSString *)_flag add:(NSNumber *)_add;
60 - (NSArray *)fetchMessagesFrom:(unsigned)_from to:(unsigned)_to;
62 - (void)resetQualifierCache;
64 - (void)setRecent:(NSNumber *)_rec exists:(NSNumber *)_exists;
65 - (void)clearParentFolder;
67 - (BOOL)_testMessages:(NSArray *)_msgs operation:(NSString *)_op;
68 - (NSArray *)_getMsnRanges:(NSArray *)_msgs;
69 - (NSArray *)_calculateSequences:(NSMutableArray *)_numbers count:(int)_cnt;
71 - (NSNotificationCenter *)notificationCenter;
72 - (void)_registerForNotifications;
74 @end /* NGImap4Folder(Private) */
77 @implementation NGImap4Folder
79 static NSNumber *YesNumber = nil;
80 static NSNumber *NoNumber = nil;
81 static NSArray *StatusFlags = nil;
82 static NSArray *UnseenFlag = nil;
83 static BOOL ImapDebugEnabled = NO;
85 static int ShowNonExistentFolder = -1;
86 static int IgnoreHasNoChildrenFlag = -1;
87 static int FetchNewUnseenMessagesInSubFoldersOnDemand = -1;
90 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
91 static BOOL didInit = NO;
95 YesNumber = [[NSNumber numberWithBool:YES] retain];
96 NoNumber = [[NSNumber numberWithBool:NO] retain];
98 StatusFlags = [[NSArray alloc]
99 initWithObjects:@"messages", @"recent", @"unseen", nil];
100 UnseenFlag = [[NSArray alloc] initWithObjects:@"unseen", nil];
102 ShowNonExistentFolder = [ud boolForKey:@"ShowNonExistentFolder"] ? 1 : 0;
103 IgnoreHasNoChildrenFlag = [ud boolForKey:@"IgnoreHasNoChildrenFlag"] ? 1 : 0;
104 ImapDebugEnabled = [ud boolForKey:@"ImapDebugEnabled"];
106 FetchNewUnseenMessagesInSubFoldersOnDemand =
107 [ud boolForKey:@"FetchNewUnseenMessagesInSubFoldersOnDemand"] ? 1 : 0;
112 [self logWithFormat:@"ERROR: cannot init NGImap4Folder with -init!"];
113 [self doesNotRecognizeSelector:_cmd];
117 - (void)_setupMessageCache {
118 #if USE_MESSAGE_CACHE
120 self->qualifierCache =
121 [[NSMutableArray alloc] initWithCapacity:MAX_QUALIFIER_CACHE];
122 self->messagesCache =
123 [[NSMutableArray alloc] initWithCapacity:MAX_QUALIFIER_CACHE];
127 - (id)initWithContext:(NGImap4Context *)_context
128 name:(NSString *)_name
129 flags:(NSArray *)_flags
130 parentFolder:(id<NGImap4Folder>)_folder
132 if ((self = [super init])) {
133 self->context = [_context retain];
134 self->flags = [[NGImap4FolderFlags alloc] initWithFlagArray:_flags];
135 self->name = [_name copy];
136 self->parentFolder = _folder;
137 self->mailRegistry = [[NGImap4FolderMailRegistry alloc] init];
139 /* mark as 'to be fetched' */
143 self->usedSpace = -1;
145 self->overQuota = -1;
147 self->failedFlags.status = NO;
148 self->failedFlags.select = NO;
149 self->failedFlags.quota = NO;
151 // TODO: this also looks pretty weird!
152 if ([[self->name lowercaseString] isEqualToString:@"/inbox"] &&
153 [self->flags doNotSelectFolder]) {
156 [self resetLastException];
158 res = [[self->context client] subscribe:[self absoluteName]];
160 if ([self lastException] != nil) {
165 if ([[res objectForKey:@"result"] boolValue])
166 [self->flags allowFolderSelect];
169 [self _registerForNotifications];
170 [self _setupMessageCache];
172 #if NGIMAP_FOLDER_DEBUG
173 return [self->context registerFolder:self];
180 [[self notificationCenter] removeObserver:self];
181 [self->context removeSelectedFolder:self];
182 [self->subFolders makeObjectsPerformSelector:@selector(clearParentFolder)];
184 [self->mailRegistry release];
185 [self->msn2UidCache release];
186 [self->context release];
187 [self->flags release];
188 [self->name release];
189 [self->subFolders release];
190 [self->messageFlags release];
192 #if USE_MESSAGE_CACHE
193 [self->messages release];
194 [self->qualifierCache release];
195 [self->messagesCache release];
197 self->isReadOnly = nil;
198 self->parentFolder = nil;
203 - (BOOL)isEqual:(id)_obj {
206 if ([_obj isKindOfClass:[NGImap4Folder class]])
207 return [self isEqualToImap4Folder:_obj];
211 - (BOOL)isEqualToImap4Folder:(NGImap4Folder *)_folder {
214 if (([[_folder absoluteName] isEqualToString:self->name]) &&
215 [_folder context] == self->context) {
223 - (NSException *)lastException {
224 return [self->context lastException];
226 - (void)resetLastException {
227 [self->context resetLastException];
230 - (NGImap4Context *)context {
231 return self->context;
235 return [[self absoluteName] lastPathComponent];
238 - (NSString *)absoluteName {
239 // TODO: sometimes this contains a name with no / in front (eg Dovecot on
240 // MacOSX). Find out why this is.
245 return [self->flags flagArray];
248 - (NSArray *)messages {
249 return [self initializeMessages];
252 - (BOOL)_checkResult:(NSDictionary *)_dict cmd:(const char *)_command {
253 return _checkResult(self->context, _dict, _command);
256 - (NSArray *)messagesForQualifier:(EOQualifier *)_qualifier
259 // TODO: split up method
260 NSMutableArray *mes = nil;
261 NSMutableArray *msn = nil;
262 NSDictionary *dict = nil;
263 NSAutoreleasePool *pool = nil;
265 if ([self->flags doNotSelectFolder] || self->failedFlags.select)
268 if (![self->context registerAsSelectedFolder:self])
271 pool = [[NSAutoreleasePool alloc] init];
273 #if USE_MESSAGE_CACHE
274 if (self->cacheIdx > 0) {
275 NSEnumerator *qualifierEnum = nil;
276 EOQualifier *qual = nil;
279 qualifierEnum = [self->qualifierCache objectEnumerator];
281 while ((qual = [qualifierEnum nextObject])) {
282 if ([qual isEqual:_qualifier]) {
285 m = [[self->messagesCache objectAtIndex:cnt] retain];
287 return [m autorelease];
293 [self resetLastException];
295 dict = [[self->context client] searchWithQualifier:_qualifier];
297 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
300 msn = [[dict objectForKey:@"search"] mutableCopy];
302 if ((msn == nil) || ![msn isNotEmpty]) {
303 mes = [NSArray array];
306 NSEnumerator *seq = nil;
307 NSDictionary *obj = nil;
309 mes = [NSMutableArray arrayWithCapacity:512];
310 seq = [[self _calculateSequences:msn count:_cnt] objectEnumerator];
311 while ((obj = [seq nextObject])) {
314 a = [self fetchMessagesFrom:
315 [[obj objectForKey:@"start"] unsignedIntValue]
316 to:[[obj objectForKey:@"end"] unsignedIntValue]];
318 if ([self lastException] != nil)
322 [mes addObjectsFromArray:a];
324 mes = [[mes copy] autorelease];
328 #if USE_MESSAGE_CACHE
329 if (self->cacheIdx == 5)
332 if ([self->qualifierCache count] == self->cacheIdx)
333 [self->qualifierCache addObject:_qualifier];
335 [self->qualifierCache replaceObjectAtIndex:self->cacheIdx
336 withObject:_qualifier];
337 if ([self->messagesCache count] == self->cacheIdx)
338 [self->messagesCache addObject:mes];
340 [self->messagesCache replaceObjectAtIndex:self->cacheIdx
349 if ([self lastException]) {
353 return [mes autorelease];
356 - (NSArray *)messagesForQualifier:(EOQualifier *)_qualifier {
357 return [self messagesForQualifier:_qualifier maxCount:-1];
360 - (NSArray *)messageFlags {
361 if (self->messageFlags == nil)
362 [self->context registerAsSelectedFolder:self];
363 return self->messageFlags;
366 - (NSArray *)subFolders {
367 if (self->subFolders == nil)
368 [self initializeSubFolders];
369 return self->subFolders;
372 - (NGImap4Folder *)subFolderWithName:(NSString *)_name
373 caseInsensitive:(BOOL)_caseIns
375 return _subFolderWithName(self, _name, _caseIns);
378 - (id<NGImap4Folder>)parentFolder {
379 return self->parentFolder;
383 if (self->isReadOnly == nil)
384 [self->context registerAsSelectedFolder:self];
385 return (self->isReadOnly == YesNumber) ? YES : NO;
391 return [self->flags doNotSelectFolder];
393 - (BOOL)noinferiors {
394 return [self->flags doesNotSupportSubfolders];
396 - (BOOL)nonexistent {
397 return [self->flags doesNotExist];
399 - (BOOL)haschildren {
400 return [self->flags hasSubfolders];
402 - (BOOL)hasnochildren {
403 return [self->flags hasNoSubfolders];
406 return [self->flags isMarked];
409 return [self->flags isUnmarked];
413 if (self->exists == -1) {
420 if (self->recent == -1)
427 if (self->unseen == -1)
433 - (BOOL)isOverQuota {
434 if (self->overQuota == -1)
435 [self->context registerAsSelectedFolder:self];
437 return (self->overQuota == 1)? YES : NO;
441 if (self->usedSpace == -1)
444 return self->usedSpace;
448 if (self->maxQuota == -1)
451 return self->maxQuota;
454 - (BOOL)hasNewMessagesSearchRecursiv:(BOOL)_rec fetchOnDemand:(BOOL)_fetch {
456 if (([self recent] > 0) && ([self unseen] > 0))
460 if ((self->recent > 0) && (self->unseen > 0))
465 return _hasNewMessagesInSubFolder(self, _fetch);
470 - (BOOL)hasNewMessagesSearchRecursiv:(BOOL)_recursiv {
471 if (([self recent] > 0) && ([self unseen] > 0))
475 return _hasNewMessagesInSubFolder(self,
476 FetchNewUnseenMessagesInSubFoldersOnDemand);
481 - (BOOL)hasUnseenMessagesSearchRecursiv:(BOOL)_rec fetchOnDemand:(BOOL)_fetch {
483 if ([self unseen] > 0)
487 if (self->unseen > 0)
491 return _hasUnseenMessagesInSubFolder(self, _fetch);
496 - (BOOL)hasUnseenMessagesSearchRecursiv:(BOOL)_recursiv {
497 if ([self unseen] > 0)
502 _hasUnseenMessagesInSubFolder(self,
503 FetchNewUnseenMessagesInSubFoldersOnDemand);
507 /* notifications (fix that junk!) */
509 - (NSNotificationCenter *)notificationCenter {
510 static NSNotificationCenter *nc = nil;
512 nc = [[NSNotificationCenter defaultCenter] retain];
516 - (NSString *)resetFolderNotificationName {
517 return [@"NGImap4FolderReset_" stringByAppendingString:[self absoluteName]];
519 - (NSString *)resetSubfolderNotificationName {
520 return [@"NGImap4SubFolderReset__"
521 stringByAppendingString:[self absoluteName]];
524 - (void)_registerForNotifications {
525 NSNotificationCenter *nc;
528 nc = [self notificationCenter];
529 n = [self absoluteName];
531 // TODO: fix that junk!
532 if ([n isNotEmpty]) {
533 [nc addObserver:self selector:@selector(_resetFolder)
534 name:[self resetFolderNotificationName]
536 [nc addObserver:self selector:@selector(_resetSubFolder)
537 name:[self resetSubfolderNotificationName]
542 - (void)_postResetFolderNotification {
543 [[self notificationCenter] postNotificationName:
544 [self resetFolderNotificationName]
547 - (void)_postResetSubfolderNotification {
548 [[self notificationCenter] postNotificationName:
549 [self resetSubfolderNotificationName]
553 /* private methods */
555 - (NSArray *)initializeMessages {
556 return [self initializeMessagesFrom:0 to:[self exists]];
559 - (NSArray *)initializeMessagesFrom:(unsigned)_from to:(unsigned)_to {
560 #if USE_MESSAGE_CACHE
561 if (self->messages == nil) {
562 self->messages = [[NSMutableArray alloc] initWithCapacity:_to];
564 [self->messages addObjectsFromArray:[self fetchMessagesFrom:_from to:_to]];
565 return self->messages;
567 return [self fetchMessagesFrom:_from to:_to];
571 - (NGImap4Message *)createMessageForUid:(unsigned)_uid
572 headers:(id)_headers size:(unsigned)_size flags:(NSArray *)_flags
574 return [[NGImap4Message alloc] initWithUid:_uid
575 headers:_headers size:_size flags:_flags
576 folder:self context:self->context];
579 - (NSArray *)_buildMessagesFromFetch:(NSDictionary *)_fetch
580 usingMessages:(NSDictionary *)_messages
584 NGMimeMessageParser *parser;
586 NSAutoreleasePool *pool;
588 pool = [[NSAutoreleasePool alloc] init];
589 mEnum = [[_fetch objectForKey:@"fetch"] objectEnumerator];
592 if (_messages == nil)
593 mes = [[NSMutableArray alloc] initWithCapacity:512];
595 parser = [[[NGMimeMessageParser alloc] init] autorelease];
596 // TODO: should we disable parsing of some headers? but which?
597 // is this method only for parsing headers?!
599 while ((m = [mEnum nextObject])) {
600 NGDataStream *stream = nil;
602 id headers, uid, f, size;
604 headerData = [m objectForKey:@"header"];
605 uid = [m objectForKey:@"uid"];
606 f = [m objectForKey:@"flags"];
607 size = [m objectForKey:@"size"];
609 if (headerData == nil || uid == nil || f == nil || size == nil) {
610 [self logWithFormat:@"WARNING[%s]: got no header, uid, flags, size "
611 @"for %@", __PRETTY_FUNCTION__, m];
614 if (([f containsObject:@"recent"]) && ([f containsObject:@"seen"])) {
616 [f removeObject:@"recent"];
621 stream = [[NGDataStream alloc] initWithData:headerData
622 mode:NGStreamMode_readOnly];
623 [parser prepareForParsingFromStream:stream];
624 [stream release]; stream = nil;
627 headers = [parser parseHeader];
632 if ((msg = [_messages objectForKey:uid]) == nil) {
633 [self logWithFormat:@"WARNING[%s]: missing message for uid %@ from "
634 @"fetch %@ in dict %@", __PRETTY_FUNCTION__,
635 uid, _fetch, _messages];
638 [msg _setHeaders:headers size:[size intValue] flags:f];
643 m = [self createMessageForUid:[uid unsignedIntValue]
644 headers:headers size:[size unsignedIntValue] flags:f];
645 if (m) [mes addObject:m];
650 [mes release]; mes = nil;
653 return [m autorelease];;
656 - (NSArray *)_buildMessagesFromFetch:(NSDictionary *)_fetch {
657 return [self _buildMessagesFromFetch:_fetch usingMessages:nil];
660 - (NSArray *)_messageIds:(NSArray *)_so onlyUnseen:(BOOL)_unseen {
661 NSAutoreleasePool *pool;
664 static EOQualifier *UnseenQual = nil;
668 /* hack for sorting for unseen/seen */
669 if (UnseenQual == nil) {
670 UnseenQual = [[EOKeyValueQualifier alloc]
673 EOQualifierOperatorEqual
677 pool = [[NSAutoreleasePool alloc] init];
679 if ([_so count] == 1) {
682 so = [_so lastObject];
684 if ([[so key] isEqualToString:@"unseen"]) {
685 static NSArray *DateSo = nil;
686 static EOQualifier *SeenQual = nil;
688 NSMutableArray *muids;
689 EOQualifier *qual1, *qual2;
692 DateSo = [[NSArray alloc] initWithObjects:
693 [EOSortOrdering sortOrderingWithKey:@"date"
694 selector:[so selector]], nil];
696 if (SeenQual == nil) {
697 SeenQual = [[EOKeyValueQualifier alloc]
700 EOQualifierOperatorEqual
703 muids = [[NSMutableArray alloc] initWithCapacity:255];
705 if (sel_eq([so selector], EOCompareAscending) ||
706 sel_eq([so selector], EOCompareCaseInsensitiveAscending)) {
723 dict = [[self->context client] sort:DateSo qualifier:qual1
724 encoding:[self->context sortEncoding]];
726 if (![[dict objectForKey:@"result"] boolValue]) {
727 [self logWithFormat:@"ERROR[%s](1): sort failed (sortOrderings %@, "
728 @"qual1 %@)", __PRETTY_FUNCTION__, DateSo, qual1];
731 [muids addObjectsFromArray:[dict objectForKey:@"sort"]];
734 dict = [[self->context client] sort:DateSo qualifier:qual2
735 encoding:[self->context sortEncoding]];
737 if (![[dict objectForKey:@"result"] boolValue]) {
738 [self logWithFormat:@"ERROR[%s](2): sort failed (sortOrderings %@, "
739 @"qual2 %@ ", __PRETTY_FUNCTION__, DateSo, qual2];
742 [muids addObjectsFromArray:[dict objectForKey:@"sort"]];
745 [muids release]; muids = nil;
751 if (![_so isNotEmpty]) {
752 static NSArray *ArrivalSO = nil;
754 if (ArrivalSO == nil) {
758 [EOSortOrdering sortOrderingWithKey:@"arrival"
759 selector:EOCompareAscending], nil];
769 [self resetLastException];
771 dict = [[self->context client] sort:_so qualifier:qual
772 encoding:[self->context sortEncoding]];
774 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
777 uids = [[dict objectForKey:@"sort"] retain];
779 [pool release]; pool = nil;
781 return [uids autorelease];
784 - (NSData *)blobForUid:(unsigned)_mUid part:(NSString *)_part {
786 called by NGImap4Message -contentsOfPart:
788 NSDictionary *result;
789 NSArray *fetchResults;
791 NSArray *uids, *parts;
793 if (![self->context registerAsSelectedFolder:self])
796 bodyKey = [NSString stringWithFormat:
797 @"body[%@]", _part ? _part : (NSString *)@""];
798 uids = [NSArray arrayWithObject:[NSNumber numberWithUnsignedInt:_mUid]];
799 parts = [NSArray arrayWithObject:bodyKey];
801 result = [[self->context client] fetchUids:uids parts:parts];
802 if (![self _checkResult:result cmd:__PRETTY_FUNCTION__]) {
803 [self debugWithFormat:@"Note: _checkResult: rejected result %d/%@: %@",
804 _mUid, _part, result];
807 else if (result == nil) {
808 [self debugWithFormat:@"Note: got no result for %d/%@", _mUid, _part];
812 fetchResults = [result objectForKey:@"fetch"];
813 if (![fetchResults isNotEmpty])
814 [self debugWithFormat:@"found no fetch result"];
816 // TODO: using 'lastObject' is certainly wrong? need to search for body
817 result = [fetchResults lastObject];
819 if ((result = [result objectForKey:@"body"]) == nil)
820 [self debugWithFormat:@"found no body in fetch results: %@", fetchResults];
822 return [result objectForKey:@"data"];
825 - (NGImap4Message *)messageForUid:(unsigned)_mUid
826 sortOrderings:(NSArray *)_so
827 onlyUnread:(BOOL)_unread
828 nextMessage:(BOOL)_next
830 NSArray *uids, *allSortUids;
831 NSEnumerator *enumerator;
832 NSNumber *uid, *eUid, *lastUid;
834 if ([self->flags doNotSelectFolder] || self->failedFlags.select)
837 uid = [NSNumber numberWithUnsignedInt:_mUid];
838 allSortUids = [self _messageIds:_so onlyUnseen:NO];
840 uids = _unread ? [self _messageIds:_so onlyUnseen:_unread] : (NSArray *)nil;
842 enumerator = [allSortUids objectEnumerator];
845 while ((eUid = [enumerator nextObject])) {
846 if ([uid isEqual:eUid])
850 if ([uids containsObject:eUid])
857 [self logWithFormat:@"WARNING[%s]: Couldn`t found next/prev message "
858 @"(missing orig. message %d for sortordering %@",
859 __PRETTY_FUNCTION__, _mUid, _so];
864 while ((uid = [enumerator nextObject])) {
865 if ([uids containsObject:uid])
870 uid = [enumerator nextObject];
878 return [[[NGImap4Message alloc] initWithUid:[uid unsignedIntValue]
879 folder:self context:self->context]
884 build NGImap4Messages with sorted uids
887 - (NSArray *)fetchSortedMessages:(NSArray *)_so {
888 NSArray *uids, *array;
889 NSMutableArray *marray;
890 NSAutoreleasePool *pool;
891 NSEnumerator *enumerator;
894 if ([self->flags doNotSelectFolder] || self->failedFlags.select)
897 if (![self->context registerAsSelectedFolder:self])
900 pool = [[NSAutoreleasePool alloc] init];
902 if (![_so isNotEmpty])
903 return [self messages];
905 if (!(uids = [self _messageIds:_so onlyUnseen:NO]))
906 return [self messages];
908 enumerator = [uids objectEnumerator];
909 marray = [[NSMutableArray alloc] initWithCapacity:[uids count]];
911 while ((uid = [enumerator nextObject])) {
914 m = [[NGImap4Message alloc] initWithUid:[uid intValue]
915 folder:self context:self->context];
916 if (m) [marray addObject:m];
919 array = [marray shallowCopy];
921 [marray release]; marray = nil;
922 [pool release]; pool = nil;
924 return [array autorelease];
928 fetch headers for _array in range (for use with fetchSortedMessages)
931 - (void)bulkFetchHeadersFor:(NSArray *)_array inRange:(NSRange)_aRange
932 withAllUnread:(BOOL)_unread
934 NSArray *messages, *uids;
935 NSAutoreleasePool *pool;
936 NSEnumerator *enumerator;
937 NGImap4Message *message;
938 NSMutableDictionary *messageMapping;
942 if ([self->flags doNotSelectFolder])
945 if (_aRange.length == 0)
948 if (![self->context registerAsSelectedFolder:self])
951 pool = [[NSAutoreleasePool alloc] init];
953 if (_aRange.location >= [_array count]) {
956 if (_aRange.location + _aRange.length > [_array count]) {
957 _aRange.length = [_array count] - _aRange.location;
959 messages = [_array subarrayWithRange:_aRange];
966 q = [EOQualifier qualifierWithQualifierFormat:@"flags = \"unseen\""];
967 d = [[self->context client] searchWithQualifier:q];
969 if ([[d objectForKey:@"result"] boolValue])
970 unreadUids = [d objectForKey:@"search"];
972 enumerator = [messages objectEnumerator];
973 messageMapping = [NSMutableDictionary dictionaryWithCapacity:
975 while ((message = [enumerator nextObject]) != nil) {
976 if (![message isComplete]) {
977 [messageMapping setObject:message
978 forKey:[NSNumber numberWithUnsignedInt:[message uid]]];
981 if ([unreadUids isNotEmpty]) {
982 enumerator = [_array objectEnumerator];
983 while ((message = [enumerator nextObject])) {
986 number = [NSNumber numberWithUnsignedInt:[message uid]];
988 if ([unreadUids containsObject:number])
989 [messageMapping setObject:message forKey:number];
992 if ([messageMapping isNotEmpty]) {
993 static NSArray *sortKeys = nil;
995 uids = [messageMapping allKeys];
997 if (sortKeys == nil) {
998 sortKeys = [[NSArray alloc] initWithObjects:@"uid",
1000 @"rfc822.size", @"flags",
1003 dict = [[self->context client] fetchUids:uids parts:sortKeys];
1004 if ([self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1005 [self _buildMessagesFromFetch:dict usingMessages:messageMapping];
1007 if (_unread) { /* set unfetched messeges to unread */
1008 NSEnumerator *enumerator;
1011 enumerator = [_array objectEnumerator];
1013 while ((m = [enumerator nextObject])) {
1016 n = [NSNumber numberWithUnsignedInt:[m uid]];
1018 if (![uids containsObject:n])
1024 [pool release]; pool = nil;
1028 - (void)bulkFetchHeadersFor:(NSArray *)_array inRange:(NSRange)_aRange {
1029 [self bulkFetchHeadersFor:_array inRange:_aRange withAllUnread:NO];
1033 fetch only sorted messages in range
1036 - (NSArray *)fetchSortedMessages:(NSRange)_aRange
1037 sortOrderings:(NSArray *)_so
1039 static NSArray *sortKeys = nil;
1042 NSAutoreleasePool *pool;
1044 if ([self->flags doNotSelectFolder] || self->failedFlags.select)
1047 if (_aRange.length == 0)
1048 return [NSArray array];
1050 if (![self->context registerAsSelectedFolder:self])
1053 pool = [[NSAutoreleasePool alloc] init];
1055 if ((uids = [self _messageIds:_so onlyUnseen:NO]) == nil)
1058 if (_aRange.location + _aRange.length > [uids count])
1059 _aRange.length = [uids count] - _aRange.location;
1061 uids = [uids subarrayWithRange:_aRange];
1063 if (sortKeys == nil) {
1064 sortKeys = [[NSArray alloc] initWithObjects:@"uid",
1066 @"rfc822.size", @"flags",
1069 dict = [[self->context client] fetchUids:uids parts:sortKeys];
1071 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1074 m = [[self _buildMessagesFromFetch:dict] retain];
1076 return [m autorelease];
1079 - (NSArray *)fetchMessagesFrom:(unsigned)_from to:(unsigned)_to {
1080 static NSArray *sortKeys = nil;
1081 NSAutoreleasePool *pool;
1085 if ([self->flags doNotSelectFolder])
1091 if (![self->context registerAsSelectedFolder:self])
1095 return [NSArray array];
1097 pool = [[NSAutoreleasePool alloc] init];
1099 [self resetLastException];
1101 /* TODO: normalize sort-key arrays? */
1102 if (sortKeys == nil) {
1103 sortKeys = [[NSArray alloc] initWithObjects:@"uid",
1105 @"rfc822.size", @"flags",
1108 dict = [[self->context client] fetchFrom:_from to:_to parts:sortKeys];
1109 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1112 m = [[self _buildMessagesFromFetch:dict] retain];
1115 return [m autorelease];
1118 - (void)initializeSubFolders {
1120 NSEnumerator *folders;
1124 BOOL showSubsrcFolders;
1127 if ([self->flags doesNotSupportSubfolders])
1130 if (!IgnoreHasNoChildrenFlag && [self->flags hasNoSubfolders])
1133 if (self->subFolders)
1134 [self resetSubFolders];
1136 [self resetLastException];
1138 showSubsrcFolders = [self->context showOnlySubscribedInSubFolders];
1140 pattern = [[self absoluteName] stringByAppendingString:@"/%"];
1141 res = (showSubsrcFolders)
1142 ? [[self->context client] lsub:@"" pattern:pattern]
1143 : [[self->context client] list:@"" pattern:pattern];
1145 if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1148 res = [res objectForKey:@"list"];
1150 objs = calloc([res count] + 2, sizeof(id));
1153 names = [res allKeys];
1154 names = [names sortedArrayUsingSelector:
1155 @selector(caseInsensitiveCompare:)];
1156 folders = [names objectEnumerator];
1160 if (showSubsrcFolders) {
1161 n = [self absoluteName];
1165 while ((folder = [folders nextObject])) {
1166 NGImap4Folder *newFolder;
1169 f = [res objectForKey:folder];
1171 if (!ShowNonExistentFolder) {
1172 if ([f containsObject:@"nonexistent"])
1175 newFolder = [NGImap4Folder alloc]; /* to keep gcc happy */
1176 objs[cnt] = [[newFolder initWithContext:self->context
1177 name:folder flags:f parentFolder:self]
1179 if (objs[cnt] == nil)
1184 self->subFolders = [[NSArray alloc] initWithObjects:objs count:cnt];
1186 if (objs) free(objs);
1190 return [self selectImmediately:NO];
1193 - (BOOL)selectImmediately:(BOOL)_imm {
1196 if ([self->flags doNotSelectFolder]) {
1197 [self logWithFormat:@"WARNING[%s]: try to select folder with noselect "
1198 @"flag <%@>", __PRETTY_FUNCTION__, self];
1201 if (self->failedFlags.select)
1205 if ([[self context] isInSyncMode] && self->selectSyncState)
1208 [self resetLastException];
1210 dict = [[self->context client] select:[self absoluteName]];
1211 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1212 self->failedFlags.select = YES;
1215 [self->context setSelectedFolder:self];
1217 ASSIGN(self->messageFlags, [dict objectForKey:@"flags"]);
1220 [[dict objectForKey:@"access"] isEqualToString:@"READ-WRITE"]
1221 ? NoNumber : YesNumber;
1223 // TODO: isn't there a better way to check for overquota?
1224 if ([[dict objectForKey:@"alert"] isEqualToString:@"Mailbox is over quota"])
1225 self->overQuota = 1;
1227 self->overQuota = 0;
1229 [self setRecent:[dict objectForKey:@"recent"]
1230 exists:[dict objectForKey:@"exists"]];
1232 self->maxQuota = -1;
1233 self->usedSpace = -1;
1234 self->failedFlags.quota = NO;
1235 self->selectSyncState = YES;
1243 if ([self->flags doNotSelectFolder])
1246 if (self->failedFlags.status)
1249 [self->context resetLastException];
1251 dict = [[self->context client] status:[self absoluteName] flags:StatusFlags];
1253 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1257 self->maxQuota = -1;
1258 self->usedSpace = -1;
1259 self->overQuota = -1;
1260 self->failedFlags.status = YES;
1261 self->failedFlags.quota = NO;
1265 [self setRecent:[dict objectForKey:@"recent"]
1266 exists:[dict objectForKey:@"messages"]];
1267 self->unseen = [[dict objectForKey:@"unseen"] intValue];
1274 - (void)resetFolder {
1275 // TODO: shouldn't the post happen after the expunge?
1276 [self _postResetFolderNotification];
1280 - (void)_resetFolder {
1281 #if USE_MESSAGE_CACHE
1282 [self resetQualifierCache];
1284 [self->msn2UidCache release]; self->msn2UidCache = nil;
1285 [self->flags release]; self->flags = nil;
1286 [self->messageFlags release]; self->messageFlags = nil;
1287 #if USE_MESSAGE_CACHE
1288 [self->messages release]; self->messages = nil;
1290 self->maxQuota = -1;
1291 self->usedSpace = -1;
1292 self->overQuota = -1;
1293 self->failedFlags.select = NO;
1294 self->failedFlags.quota = NO;
1295 self->isReadOnly = nil;
1299 - (void)resetStatus {
1303 self->failedFlags.status = NO;
1306 - (void)_resetSubFolder {
1309 ctx = [self context];
1311 if ((self->parentFolder == nil) || (self == [ctx inboxFolder]))
1312 [ctx resetSpecialFolders];
1314 [self->subFolders release]; self->subFolders = nil;
1317 - (void)resetSubFolders {
1318 // TODO: explain in detail what this does
1321 n = [self absoluteName];
1323 [self _postResetSubfolderNotification];
1325 [self _resetSubFolder];
1328 - (BOOL)renameTo:(NSString *)_name {
1332 if ([self isReadOnly])
1335 if ((_name == nil) || ([_name length] == 0))
1338 n = [[self->name stringByDeletingLastPathComponent]
1339 stringByAppendingPathComponent:_name];
1341 [self resetLastException];
1343 dict = [[self->context client] rename:[self absoluteName] to:n];
1345 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1348 ASSIGNCOPY(self->name, n);
1349 [self->globalID release]; self->globalID = nil;
1351 [self resetSubFolders];
1353 dict = [[self->context client] subscribe:self->name];
1355 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1363 - (BOOL)deleteSubFolder:(NGImap4Folder *)_folder {
1364 if ([self isReadOnly])
1366 return _deleteSubFolder(self, _folder);;
1369 - (BOOL)copySubFolder:(NGImap4Folder *)_f to:(NGImap4Folder *)_folder {
1370 return _copySubFolder(self, _f, _folder);
1373 - (BOOL)moveSubFolder:(NGImap4Folder *)_f to:(NGImap4Folder *)_folder {
1374 if ([self isReadOnly])
1376 return _moveSubFolder(self, _f, _folder);
1379 - (BOOL)createSubFolderWithName:(NSString *)_name {
1380 if ([self isReadOnly])
1382 return _createSubFolderWithName(self, _name, YES);
1388 if ([self->flags doNotSelectFolder] || self->failedFlags.select)
1391 if ([self isReadOnly])
1393 if (![self->context registerAsSelectedFolder:self])
1396 dict = [[self->context client] expunge];
1397 if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1400 [self setRecent:[dict objectForKey:@"recent"]
1401 exists:[dict objectForKey:@"exists"]];
1404 - (BOOL)addFlag:(NSString *)_flag toMessages:(NSArray *)_messages {
1405 return [self flag:_flag toMessages:_messages add:YesNumber];
1408 - (BOOL)removeFlag:(NSString *)_flag fromMessages:(NSArray *)_messages {
1409 return [self flag:_flag toMessages:_messages add:NoNumber];
1412 - (BOOL)flag:(NSString *)_flag toMessages:(NSArray *)_messages
1413 add:(NSNumber *)_add
1415 NSEnumerator *enumerator;
1416 NGImap4Message *message;
1421 add = [_add boolValue];
1423 if ([self->flags doNotSelectFolder])
1427 [self logWithFormat:@"WARNING[%s]: try to set an empty flag",
1428 __PRETTY_FUNCTION__];
1431 if ([self isReadOnly])
1434 if (![self->context registerAsSelectedFolder:self])
1437 if (![self _testMessages:_messages operation:@"store"])
1440 [self resetLastException];
1442 enumerator = [[self _getMsnRanges:_messages] objectEnumerator];
1443 if (enumerator == nil) {
1445 [self->context removeSelectedFolder:self];
1449 flagArray = [_flag isNotNull] ? [NSArray arrayWithObject:_flag] : nil;
1450 while ((obj = [enumerator nextObject])) {
1452 int objEnd, objStart;
1454 if ((objEnd = [[obj objectForKey:@"end"] unsignedIntValue]) <= 0)
1457 objStart = [[obj objectForKey:@"start"] unsignedIntValue];
1458 res = [[self->context client]
1459 storeFrom:objStart to:objEnd
1460 add:_add flags:flagArray];
1462 if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1468 enumerator = [_messages objectEnumerator];
1469 while ((message = [enumerator nextObject])) {
1471 [message addFlag:_flag];
1473 [message removeFlag:_flag];
1479 - (BOOL)flagToAllMessages:(NSString *)_flag add:(NSNumber *)_add {
1480 #if USE_MESSAGE_CACHE
1481 BOOL add = [_add boolValue];
1482 NSEnumerator *enumerator = nil;
1483 NGImap4Message *m = nil;
1486 if ([self->flags doNotSelectFolder])
1492 if ([self isReadOnly])
1495 if (![self->context registerAsSelectedFolder:self])
1499 if ([self exists] > 0) {
1502 [self resetLastException];
1504 res = [[self->context client] storeFrom:0 to:[self exists]
1506 flags:[NSArray arrayWithObject:_flag]];
1508 if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1511 #if USE_MESSAGE_CACHE
1512 enumerator = [self->messages objectEnumerator];
1513 while ((m = [enumerator nextObject])) {
1516 : [m removeFlag:_flag];
1523 - (BOOL)deleteAllMessages {
1524 if ([self isReadOnly])
1527 if ([self flagToAllMessages:@"Deleted" add:YesNumber]) {
1534 - (BOOL)deleteMessages:(NSArray *)_messages {
1535 if ([self isReadOnly])
1538 if ([self addFlag:@"Deleted" toMessages:_messages]) {
1545 - (BOOL)moveMessages:(NSArray *)_messages toFolder:(NGImap4Folder *)_folder {
1546 if ([self isReadOnly])
1549 if (_folder != nil) {
1550 if ([self copyMessages:_messages toFolder:_folder]) {
1551 return [self deleteMessages:_messages];
1557 - (BOOL)copyMessages:(NSArray *)_messages toFolder:(NGImap4Folder *)_folder {
1558 NSEnumerator *enumerator;
1560 NSString *folderName;
1562 if ([self->flags doNotSelectFolder])
1565 folderName = [_folder absoluteName];
1567 if (_folder == nil) {
1568 [self logWithFormat:@"WARNING[%s]: try to copy to nil folder",
1569 __PRETTY_FUNCTION__];
1572 [self resetLastException];
1574 if ([_folder isReadOnly]) {
1575 NGImap4ResponseException *exc;
1577 [self logWithFormat:@"WARNING[%s]: try to copy to readonly folder %@",
1578 __PRETTY_FUNCTION__, _folder];
1580 exc = [[NGImap4ResponseException alloc] initWithFormat:
1581 @"copy to read only folder"];
1582 [self->context setLastException:exc];
1583 [exc release]; exc = nil;
1586 if (![self->context registerAsSelectedFolder:self])
1589 if (![self _testMessages:_messages operation:@"copy"])
1592 enumerator = [[self _getMsnRanges:_messages] objectEnumerator];
1593 if (enumerator == nil) {
1595 [self->context removeSelectedFolder:self];
1599 [self resetLastException];
1600 if (![self->context registerAsSelectedFolder:self])
1603 while ((obj = [enumerator nextObject])) {
1606 objEnd = [[obj objectForKey:@"end"] unsignedIntValue];
1612 start = [[obj objectForKey:@"start"] unsignedIntValue];
1613 res = [[self->context client]
1614 copyFrom:start to:objEnd toFolder:folderName];
1615 if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1619 [_folder resetFolder];
1620 return (obj == nil) ? YES : NO;
1623 - (BOOL)appendMessage:(NSData *)_msg {
1624 if ([self isReadOnly])
1630 dict = [[self->context client]
1631 append:_msg toFolder:[self absoluteName]
1632 withFlags:[NSArray arrayWithObject:@"seen"]];
1634 if ([self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1642 #if USE_MESSAGE_CACHE
1643 - (void)resetQualifierCache {
1645 [self->qualifierCache removeAllObjects];
1651 - (void)setRecent:(NSNumber *)_rec exists:(NSNumber *)_exists {
1652 #if USE_MESSAGE_CACHE
1653 BOOL resetQualifier = NO;
1655 int tmp = [_rec intValue];
1656 if (self->recent != tmp) {
1658 resetQualifier = YES;
1661 if (_exists != nil) {
1662 int tmp = [_exists intValue];
1663 if (self->exists != tmp) {
1665 resetQualifier = YES;
1669 [self resetQualifierCache];
1672 if (_exists != nil) {
1675 e = [_exists intValue];
1685 r = [_rec intValue];
1687 if ((e != self->exists) || (r != self->recent)) {
1698 - (void)processResponse:(NSDictionary *)_dict {
1699 #if USE_MESSAGE_CACHE
1700 id exp = [_dict objectForKey:@"expunge"];
1703 [self setRecent:[_dict objectForKey:@"recent"]
1704 exists:[_dict objectForKey:@"exists"]];
1706 #if USE_MESSAGE_CACHE
1707 if ((exp != nil) && ([exp count] > 0) && ([self->messages count] > 0)) {
1708 NSEnumerator *enumerator;
1711 enumerator = [exp objectEnumerator];
1712 while ((obj = [enumerator nextObject])) {
1713 int n = [obj intValue] - 1;
1715 [self->messages removeObjectAtIndex:n];
1717 [self resetQualifierCache];
1723 id<NGImap4Folder> f, trash;
1725 trash = [self->context trashFolder];
1728 [self logWithFormat:@"WARNING[%s]: No trash folder was set",
1729 __PRETTY_FUNCTION__];
1733 for (f = self; f; f = [f parentFolder]) {
1734 if ([f isEqual:trash])
1741 NSEnumerator *enumerator;
1744 self->selectSyncState = NO;
1746 if (self->subFolders == nil)
1749 enumerator = [[self subFolders] objectEnumerator];
1750 while ((folder = [enumerator nextObject]))
1758 if (self->url != nil)
1761 if ((base = [self->context url]) == nil) {
1762 [self logWithFormat:@"ERROR: got no URL for context: %@", self->context];
1766 if ((p = [self absoluteName]) == nil)
1769 if (![p hasPrefix:@"/"]) p = [@"/" stringByAppendingString:p];
1770 self->url = [[NSURL alloc]
1771 initWithScheme:[base scheme] host:[base host] path:p];
1775 - (EOGlobalID *)serverGlobalID {
1776 return [self->context serverGlobalID];
1778 - (EOGlobalID *)globalID {
1780 return self->globalID;
1782 self->globalID = [[NGImap4FolderGlobalID alloc] initWithServerGlobalID:
1783 [self serverGlobalID]
1785 [self absoluteName]];
1786 return self->globalID;
1789 /* quota information */
1793 NSDictionary *quota;
1795 if (self->failedFlags.quota)
1798 if (![self->context canQuota]) {
1799 [self logWithFormat:@"WARNING[%s] call quota but capability contains"
1800 @" no quota string", __PRETTY_FUNCTION__];
1803 n = [self absoluteName];
1804 [self resetLastException];
1806 if ([self->flags doNotSelectFolder])
1809 quota = [[self->context client] getQuotaRoot:n];
1811 if (![self _checkResult:quota cmd:__PRETTY_FUNCTION__]) {
1812 self->failedFlags.quota = YES;
1816 quota = [quota objectForKey:@"quotas"];
1817 quota = [quota objectForKey:n];
1819 self->maxQuota = [[quota objectForKey:@"maxQuota"] intValue];
1820 self->usedSpace = [[quota objectForKey:@"usedSpace"] intValue];
1824 - (BOOL)_testMessages:(NSArray *)_messages operation:(NSString *)_operation {
1825 NSEnumerator *enumerator;
1828 enumerator = [_messages objectEnumerator];
1829 while ((obj = [enumerator nextObject])) {
1830 if ([obj folder] != self) {
1831 [self logWithFormat:@"ERROR: try to %@ mails in folder who didn`t own"
1832 @" this mail \nFolder %@\nMail %@ allMessages %@",
1833 _operation, self, obj, _messages];
1840 - (NSArray *)_getMsnRanges:(NSArray *)_messages {
1841 // TODO: might split up? document!
1842 static NSArray *UidKey = nil;
1845 NSMutableDictionary *map;
1846 NSMutableArray *msn;
1847 NSEnumerator *enumerator;
1849 NSAutoreleasePool *pool;
1850 NGImap4Message *message;
1852 if ([self exists] == 0)
1853 return [NSArray array];
1855 pool = [[NSAutoreleasePool alloc] init];
1857 if (UidKey == nil) {
1861 UidKey = [[NSArray alloc] initWithObjects:&objs count:1];
1864 [self resetLastException];
1866 if (![self->context registerAsSelectedFolder:self])
1869 if ([_messages count] > [self->msn2UidCache count]) {
1870 [self->msn2UidCache release];
1871 self->msn2UidCache = nil;
1874 if (!self->msn2UidCache) {
1877 res = [[self->context client] fetchFrom:1 to:[self exists] parts:UidKey];
1879 if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1882 self->msn2UidCache = [[res objectForKey:@"fetch"] retain];
1884 map = [[NSMutableDictionary alloc] initWithCapacity:
1885 [self->msn2UidCache count]];
1886 enumerator = [self->msn2UidCache objectEnumerator];
1888 while ((obj = [enumerator nextObject]))
1889 [map setObject:[obj objectForKey:@"msn"] forKey:[obj objectForKey:@"uid"]];
1891 msn = [[NSMutableArray alloc] initWithCapacity:[_messages count]];
1892 enumerator = [_messages objectEnumerator];
1893 while ((message = [enumerator nextObject])) {
1896 m = [map objectForKey:[NSNumber numberWithUnsignedInt:[message uid]]];
1899 [self logWithFormat:@"WARNING[%s]: Couldn`t map a message sequence "
1900 @"number to message %@ numbers %@ messages %@ "
1901 @"self->msn2UidCache %@",
1902 __PRETTY_FUNCTION__, message, map, _messages, self->msn2UidCache];
1910 [map release]; map = nil;
1912 result = [self _calculateSequences:msn count:-1];
1914 result = [result retain];
1915 [msn release]; msn = nil;
1917 return [result autorelease];
1920 - (NSArray *)_calculateSequences:(NSMutableArray *)_numbers count:(int)_cnt {
1921 // TODO: might split up? document! This looks pretty weird
1922 NSAutoreleasePool *pool;
1923 NSEnumerator *enumerator;
1924 NSMutableDictionary *range;
1925 NSMutableArray *ranges;
1929 pool = [[NSAutoreleasePool alloc] init];
1932 _cnt = [_numbers count];
1934 [_numbers sortUsingSelector:@selector(compare:)];
1936 ranges = [NSMutableArray arrayWithCapacity:[_numbers count]];
1937 enumerator = [_numbers objectEnumerator];
1938 buffer = [NSNumber numberWithInt:0];
1941 while (((obj = [enumerator nextObject])) && (cntMsgs < _cnt)) {
1944 range = [NSMutableDictionary dictionaryWithCapacity:2];
1945 [range setObject:buffer forKey:@"start"];
1948 if ([obj intValue] != [buffer intValue] + 1) {
1951 [range setObject:buffer forKey:@"end"];
1953 [ranges addObject:ir];
1960 [range setObject:buffer forKey:@"end"];
1961 [ranges addObject:range];
1966 d = [[NSDictionary alloc] initWithObjectsAndKeys:
1967 buffer, @"start", buffer, @"end", nil];
1968 [ranges addObject:d];
1971 range = [ranges objectAtIndex:0];
1972 if ([[range objectForKey:@"end"] intValue] == 0)
1973 [ranges removeObjectAtIndex:0];
1975 obj = [ranges copy];
1977 return [obj autorelease];
1980 - (void)clearParentFolder {
1981 self->parentFolder = nil;
1984 /* message factory */
1986 - (id)messageWithUid:(unsigned int)_uid {
1987 return [[[NGImap4Message alloc]
1988 initWithUid:_uid folder:self context:[self context]]
1992 /* message registry */
1994 - (NGImap4FolderMailRegistry *)mailRegistry {
1995 return self->mailRegistry;
2000 - (BOOL)isDebuggingEnabled {
2001 return ImapDebugEnabled;
2006 - (NSString *)description {
2007 NSMutableString *ms;
2010 ms = [NSMutableString stringWithCapacity:64];
2012 [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
2014 if ((tmp = [self name]))
2015 [ms appendFormat:@" name=%@", tmp];
2016 if ((tmp = [self absoluteName]))
2017 [ms appendFormat:@" absolute=%@", tmp];
2018 if ((tmp = [[self flags] componentsJoinedByString:@","]))
2019 [ms appendFormat:@" flags=%@", tmp];
2021 [ms appendString:@">"];
2026 @end /* NGImap4Folder */
2029 @implementation NSData(xxxx)
2030 - (NSString *)description {
2031 return [NSString stringWithFormat:@"NSData len: %d",