]> err.no Git - sope/blob - sope-mime/NGImap4/NGImap4Folder.m
fixed OGo bug #1899
[sope] / sope-mime / NGImap4 / NGImap4Folder.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 "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"
30 #include "imCommon.h"
31
32 @interface NGImap4Message(Private)
33
34 - (void)_setHeaders:(NGHashMap *)_headers
35   size:(unsigned)_size
36   flags:(NSArray *)_flags;
37
38 - (BOOL)isComplete;
39 - (void)setIsRead:(BOOL)_isRead;
40
41 @end /* NGImap4Message(Private) */
42
43
44 @interface NGImap4Context(Private)
45 - (void)setLastException:(NSException *)_exception;
46 - (void)setSelectedFolder:(NGImap4Folder *)_folder;
47 @end /* NGImap4Context(Private) */
48
49 @interface NGImap4Folder(Private)
50
51 - (void)_resetFolder;
52 - (void)_resetSubFolder;
53 - (void)quota;
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;
61 #if USE_MESSAGE_CACHE
62 - (void)resetQualifierCache;
63 #endif
64 - (void)setRecent:(NSNumber *)_rec exists:(NSNumber *)_exists;
65 - (void)clearParentFolder;
66
67 - (BOOL)_testMessages:(NSArray *)_msgs operation:(NSString *)_op;
68 - (NSArray *)_getMsnRanges:(NSArray *)_msgs;
69 - (NSArray *)_calculateSequences:(NSMutableArray *)_numbers count:(int)_cnt;
70
71 - (NSNotificationCenter *)notificationCenter;
72 - (void)_registerForNotifications;
73
74 @end /* NGImap4Folder(Private) */
75
76
77 @implementation NGImap4Folder
78
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;
84
85 static int ShowNonExistentFolder                      = -1;
86 static int IgnoreHasNoChildrenFlag                    = -1;
87 static int FetchNewUnseenMessagesInSubFoldersOnDemand = -1;
88
89 + (void)initialize {
90   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
91   static BOOL didInit = NO;
92   if (didInit) return;
93   didInit = YES;
94   
95   YesNumber = [[NSNumber numberWithBool:YES] retain];
96   NoNumber  = [[NSNumber numberWithBool:NO]  retain];
97
98   StatusFlags = [[NSArray alloc]
99                   initWithObjects:@"messages", @"recent", @"unseen", nil];
100   UnseenFlag = [[NSArray alloc] initWithObjects:@"unseen", nil];
101
102   ShowNonExistentFolder   = [ud boolForKey:@"ShowNonExistentFolder"] ? 1 : 0;
103   IgnoreHasNoChildrenFlag = [ud boolForKey:@"IgnoreHasNoChildrenFlag"] ? 1 : 0;
104   ImapDebugEnabled        = [ud boolForKey:@"ImapDebugEnabled"];
105   
106   FetchNewUnseenMessagesInSubFoldersOnDemand =
107       [ud boolForKey:@"FetchNewUnseenMessagesInSubFoldersOnDemand"] ? 1 : 0;
108 }
109
110 - (id)init {
111   [self release];
112   [self logWithFormat:@"ERROR: cannot init NGImap4Folder with -init!"];
113   [self doesNotRecognizeSelector:_cmd];
114   return nil;
115 }
116
117 - (void)_setupMessageCache {
118 #if USE_MESSAGE_CACHE
119   self->cacheIdx       = 0;    
120   self->qualifierCache =
121     [[NSMutableArray alloc] initWithCapacity:MAX_QUALIFIER_CACHE];
122   self->messagesCache  =
123     [[NSMutableArray alloc] initWithCapacity:MAX_QUALIFIER_CACHE];
124 #endif    
125 }
126
127 - (id)initWithContext:(NGImap4Context *)_context
128   name:(NSString *)_name
129   flags:(NSArray *)_flags
130   parentFolder:(id<NGImap4Folder>)_folder
131 {
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];
138     
139     /* mark as 'to be fetched' */
140     self->exists            = -1;
141     self->recent            = -1;
142     self->unseen            = -1;
143     self->usedSpace         = -1;
144     self->maxQuota          = -1;
145     self->overQuota         = -1;
146     
147     self->failedFlags.status = NO;
148     self->failedFlags.select = NO;
149     self->failedFlags.quota  = NO;
150     
151     // TODO: this also looks pretty weird!
152     if ([[self->name lowercaseString] isEqualToString:@"/inbox"] &&
153         [self->flags doNotSelectFolder]) {
154       NSDictionary *res;
155
156       [self resetLastException];
157       
158       res = [[self->context client] subscribe:[self absoluteName]];
159
160       if ([self lastException] != nil) {
161         [self release];
162         return nil;
163       }
164       
165       if ([[res objectForKey:@"result"] boolValue])
166         [self->flags allowFolderSelect];
167     }
168     
169     [self _registerForNotifications];
170     [self _setupMessageCache];
171   }
172 #if NGIMAP_FOLDER_DEBUG
173   return [self->context registerFolder:self];
174 #else  
175   return self;
176 #endif  
177 }
178
179 - (void)dealloc {
180   [[self notificationCenter] removeObserver:self];
181   [self->context removeSelectedFolder:self];
182   [self->subFolders makeObjectsPerformSelector:@selector(clearParentFolder)];
183   
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];
191   [self->url          release];
192 #if USE_MESSAGE_CACHE
193   [self->messages       release];
194   [self->qualifierCache release];
195   [self->messagesCache  release];
196 #endif
197   self->isReadOnly   = nil;
198   self->parentFolder = nil;
199   
200   [super dealloc];
201 }
202
203 - (BOOL)isEqual:(id)_obj {
204   if (self == _obj)
205     return YES;
206   if ([_obj isKindOfClass:[NGImap4Folder class]])
207     return [self isEqualToImap4Folder:_obj];
208   return NO;
209 }
210
211 - (BOOL)isEqualToImap4Folder:(NGImap4Folder *)_folder {
212   if (self == _folder)
213     return YES;
214   if (([[_folder absoluteName] isEqualToString:self->name]) &&
215       [_folder context] == self->context) {
216     return YES;
217   }
218   return NO;
219 }
220
221 /* accessors */
222
223 - (NSException *)lastException {
224   return [self->context lastException];
225 }
226 - (void)resetLastException {
227   [self->context resetLastException];
228 }
229
230 - (NGImap4Context *)context {
231   return self->context;
232 }
233
234 - (NSString *)name {
235   return [[self absoluteName] lastPathComponent];
236 }
237
238 - (NSString *)absoluteName {
239   // TODO: sometimes this contains a name with no / in front (eg Dovecot on
240   //       MacOSX). Find out why this is.
241   return self->name;
242 }
243
244 - (NSArray *)flags {
245   return [self->flags flagArray];
246 }
247
248 - (NSArray *)messages {
249   return [self initializeMessages];
250 }
251
252 - (BOOL)_checkResult:(NSDictionary *)_dict cmd:(const char *)_command {
253   return _checkResult(self->context, _dict, _command);
254 }
255
256 - (NSArray *)messagesForQualifier:(EOQualifier *)_qualifier
257   maxCount:(int)_cnt
258 {
259   // TODO: split up method
260   NSMutableArray    *mes  = nil;
261   NSMutableArray    *msn  = nil;
262   NSDictionary      *dict = nil;
263   NSAutoreleasePool *pool = nil;
264
265   if ([self->flags doNotSelectFolder] || self->failedFlags.select)
266     return nil;
267   
268   if (![self->context registerAsSelectedFolder:self])
269     return nil;
270
271   pool = [[NSAutoreleasePool alloc] init];
272
273 #if USE_MESSAGE_CACHE  
274   if (self->cacheIdx > 0) {
275     NSEnumerator *qualifierEnum = nil;
276     EOQualifier  *qual          = nil;
277     int          cnt            = 0;
278
279     qualifierEnum = [self->qualifierCache objectEnumerator];
280
281     while ((qual = [qualifierEnum nextObject])) {
282       if ([qual isEqual:_qualifier]) {
283         NSArray *m;
284         
285         m = [[self->messagesCache objectAtIndex:cnt] retain];
286         [pool release];
287         return [m autorelease];
288       }
289       cnt++;
290     }
291   }
292 #endif
293   [self resetLastException];
294   
295   dict = [[self->context client] searchWithQualifier:_qualifier];
296
297   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
298     return nil;
299
300   msn  = [[dict objectForKey:@"search"] mutableCopy];
301   
302   if ((msn == nil) || ![msn isNotEmpty]) {
303     mes = [NSArray array];
304   }
305   else {
306     NSEnumerator *seq = nil;
307     NSDictionary *obj = nil;
308     
309     mes = [NSMutableArray arrayWithCapacity:512];
310     seq = [[self _calculateSequences:msn count:_cnt] objectEnumerator];
311     while ((obj = [seq nextObject])) {
312       NSArray *a;
313       
314       a = [self fetchMessagesFrom:
315                   [[obj objectForKey:@"start"] unsignedIntValue]
316                 to:[[obj objectForKey:@"end"] unsignedIntValue]];
317       
318       if ([self lastException] != nil)
319         break;
320       
321       if (a)
322         [mes addObjectsFromArray:a];
323     }
324     mes = [[mes copy] autorelease];
325   }
326   [msn release];
327
328 #if USE_MESSAGE_CACHE  
329   if (self->cacheIdx == 5)
330     self->cacheIdx = 0;
331
332   if ([self->qualifierCache count] == self->cacheIdx)
333     [self->qualifierCache addObject:_qualifier];
334   else
335     [self->qualifierCache replaceObjectAtIndex:self->cacheIdx
336          withObject:_qualifier];
337   if ([self->messagesCache count] == self->cacheIdx)
338     [self->messagesCache addObject:mes];
339   else {
340     [self->messagesCache replaceObjectAtIndex:self->cacheIdx
341                          withObject:mes];
342   }
343   self->cacheIdx++;
344 #endif  
345   
346   mes = [mes retain];
347   [pool release];
348
349   if ([self lastException]) {
350     [mes release];
351     return nil;
352   }
353   return [mes autorelease];
354 }
355
356 - (NSArray *)messagesForQualifier:(EOQualifier *)_qualifier {
357   return [self messagesForQualifier:_qualifier maxCount:-1];
358 }
359
360 - (NSArray *)messageFlags {
361   if (self->messageFlags == nil)
362     [self->context registerAsSelectedFolder:self];
363   return self->messageFlags;
364 }
365
366 - (NSArray *)subFolders {
367   if (self->subFolders == nil)
368     [self initializeSubFolders];
369   return self->subFolders;
370 }
371
372 - (NGImap4Folder *)subFolderWithName:(NSString *)_name
373   caseInsensitive:(BOOL)_caseIns
374 {
375   return _subFolderWithName(self, _name, _caseIns);
376 }
377
378 - (id<NGImap4Folder>)parentFolder {
379   return self->parentFolder;
380 }
381
382 - (BOOL)isReadOnly {
383   if (self->isReadOnly == nil)
384     [self->context registerAsSelectedFolder:self];
385   return (self->isReadOnly == YesNumber) ? YES : NO;
386 }
387
388 /* flags */
389
390 - (BOOL)noselect {
391   return [self->flags doNotSelectFolder];
392 }
393 - (BOOL)noinferiors {
394   return [self->flags doesNotSupportSubfolders];
395 }
396 - (BOOL)nonexistent {
397   return [self->flags doesNotExist];
398 }
399 - (BOOL)haschildren {
400   return [self->flags hasSubfolders];
401 }
402 - (BOOL)hasnochildren {
403   return [self->flags hasNoSubfolders];
404 }
405 - (BOOL)marked {
406   return [self->flags isMarked];
407 }
408 - (BOOL)unmarked {
409   return [self->flags isUnmarked];
410 }
411
412 - (int)exists {
413   if (self->exists == -1) {
414     [self status];
415   }
416   return self->exists;
417 }
418
419 - (int)recent {
420   if (self->recent == -1)
421     [self status];
422   
423   return self->recent;
424 }
425
426 - (int)unseen {
427   if (self->unseen == -1)
428     [self status];
429   
430   return self->unseen;
431 }
432
433 - (BOOL)isOverQuota {
434   if (self->overQuota == -1)
435     [self->context registerAsSelectedFolder:self];
436   
437   return (self->overQuota == 1)? YES : NO;
438 }
439
440 - (int)usedSpace {
441   if (self->usedSpace == -1)
442     [self quota];
443   
444   return self->usedSpace;
445 }
446
447 - (int)maxQuota {
448   if (self->maxQuota == -1)
449     [self quota];
450   
451   return self->maxQuota;
452 }
453
454 - (BOOL)hasNewMessagesSearchRecursiv:(BOOL)_rec fetchOnDemand:(BOOL)_fetch {
455   if (_fetch) {
456     if (([self recent] > 0) && ([self unseen] > 0))
457       return YES;
458   }
459   else {
460     if ((self->recent > 0) && (self->unseen > 0))
461       return YES;
462   }
463   
464   if (_rec)
465     return _hasNewMessagesInSubFolder(self, _fetch);
466   
467   return NO;
468 }
469
470 - (BOOL)hasNewMessagesSearchRecursiv:(BOOL)_recursiv {
471   if (([self recent] > 0) && ([self unseen] > 0))
472     return YES;
473
474   if (_recursiv) {
475     return _hasNewMessagesInSubFolder(self,
476               FetchNewUnseenMessagesInSubFoldersOnDemand);
477   }
478   return NO;
479 }
480
481 - (BOOL)hasUnseenMessagesSearchRecursiv:(BOOL)_rec fetchOnDemand:(BOOL)_fetch {
482   if (_fetch) {
483     if ([self unseen] > 0)
484       return YES;
485   }
486   else
487     if (self->unseen > 0)
488       return YES;
489
490   if (_rec)
491     return _hasUnseenMessagesInSubFolder(self, _fetch);
492   
493   return NO;
494 }
495
496 - (BOOL)hasUnseenMessagesSearchRecursiv:(BOOL)_recursiv {
497   if ([self unseen] > 0)
498     return YES;
499
500   if (_recursiv)
501     return
502       _hasUnseenMessagesInSubFolder(self,
503                                     FetchNewUnseenMessagesInSubFoldersOnDemand);
504   return NO;
505 }
506
507 /* notifications (fix that junk!) */
508
509 - (NSNotificationCenter *)notificationCenter {
510   static NSNotificationCenter *nc = nil;
511   if (nc == nil)
512     nc = [[NSNotificationCenter defaultCenter] retain];
513   return nc;
514 }
515
516 - (NSString *)resetFolderNotificationName {
517   return [@"NGImap4FolderReset_" stringByAppendingString:[self absoluteName]];
518 }
519 - (NSString *)resetSubfolderNotificationName {
520   return [@"NGImap4SubFolderReset__" 
521            stringByAppendingString:[self absoluteName]];
522 }
523
524 - (void)_registerForNotifications {
525   NSNotificationCenter *nc;
526   NSString             *n;
527
528   nc = [self notificationCenter];
529   n  = [self absoluteName];
530       
531   // TODO: fix that junk!
532   if ([n isNotEmpty]) {
533     [nc addObserver:self selector:@selector(_resetFolder)
534         name:[self resetFolderNotificationName]
535         object:nil];
536     [nc addObserver:self selector:@selector(_resetSubFolder)
537         name:[self resetSubfolderNotificationName]
538         object:nil];
539   }
540 }
541
542 - (void)_postResetFolderNotification {
543   [[self notificationCenter] postNotificationName:
544                                [self resetFolderNotificationName] 
545                              object:nil];
546 }
547 - (void)_postResetSubfolderNotification {
548   [[self notificationCenter] postNotificationName:
549                                [self resetSubfolderNotificationName] 
550                              object:nil];
551 }
552
553 /* private methods */
554
555 - (NSArray *)initializeMessages {
556   return [self initializeMessagesFrom:0 to:[self exists]];
557 }
558
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];
563   }
564   [self->messages addObjectsFromArray:[self fetchMessagesFrom:_from to:_to]];
565   return self->messages;
566 #else
567   return [self fetchMessagesFrom:_from to:_to];
568 #endif  
569 }
570
571 - (NGImap4Message *)createMessageForUid:(unsigned)_uid
572   headers:(id)_headers size:(unsigned)_size flags:(NSArray *)_flags
573 {
574   return [[NGImap4Message alloc] initWithUid:_uid
575                                  headers:_headers size:_size flags:_flags
576                                  folder:self context:self->context];
577 }
578
579 - (NSArray *)_buildMessagesFromFetch:(NSDictionary *)_fetch
580   usingMessages:(NSDictionary *)_messages
581 {
582   NSEnumerator        *mEnum;
583   NSDictionary        *m;
584   NGMimeMessageParser *parser;
585   NSMutableArray      *mes;
586   NSAutoreleasePool   *pool;
587
588   pool  = [[NSAutoreleasePool alloc] init];
589   mEnum = [[_fetch objectForKey:@"fetch"] objectEnumerator];
590   mes   = nil;
591   
592   if (_messages == nil)
593     mes = [[NSMutableArray alloc] initWithCapacity:512];
594   
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?!
598   
599   while ((m = [mEnum nextObject])) {
600     NGDataStream *stream = nil;
601     NSData *headerData;
602     id headers, uid, f, size;
603     
604     headerData = [m objectForKey:@"header"];
605     uid        = [m objectForKey:@"uid"];
606     f          = [m objectForKey:@"flags"];
607     size       = [m objectForKey:@"size"];
608     
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];
612       continue;
613     }
614     if (([f containsObject:@"recent"]) && ([f containsObject:@"seen"])) {
615         f = [f mutableCopy];
616         [f removeObject:@"recent"];
617         [f autorelease];
618     }
619     
620     /* setup parser */
621     stream = [[NGDataStream alloc] initWithData:headerData 
622                                    mode:NGStreamMode_readOnly];
623     [parser prepareForParsingFromStream:stream];
624     [stream release]; stream = nil;
625     
626     /* parse */
627     headers = [parser parseHeader];
628     
629     if (_messages) {
630       NGImap4Message *msg;
631       
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];
636         continue;
637       }
638       [msg _setHeaders:headers size:[size intValue] flags:f];
639     }
640     else {
641       NGImap4Message *m;
642       
643       m = [self createMessageForUid:[uid unsignedIntValue]
644                 headers:headers size:[size unsignedIntValue] flags:f];
645       if (m) [mes addObject:m];
646       [m release];
647     }
648   }
649   m = [mes copy];
650   [mes release]; mes = nil;
651   [pool release];
652   
653   return [m autorelease];;
654 }
655
656 - (NSArray *)_buildMessagesFromFetch:(NSDictionary *)_fetch {
657   return [self _buildMessagesFromFetch:_fetch usingMessages:nil];
658 }
659
660 - (NSArray *)_messageIds:(NSArray *)_so onlyUnseen:(BOOL)_unseen {
661   NSAutoreleasePool  *pool;
662   NSDictionary       *dict;
663   NSArray            *uids;
664   static EOQualifier *UnseenQual = nil;
665
666   uids = nil;
667   
668   /* hack for sorting for unseen/seen */
669   if (UnseenQual == nil) {
670     UnseenQual = [[EOKeyValueQualifier alloc]
671                                        initWithKey:@"flags"
672                                        operatorSelector:
673                                        EOQualifierOperatorEqual
674                                        value:@"unseen"];
675   }
676
677   pool = [[NSAutoreleasePool alloc] init];
678   
679   if ([_so count] == 1) {
680     EOSortOrdering *so;
681
682     so = [_so lastObject];
683
684     if ([[so key] isEqualToString:@"unseen"]) {
685       static NSArray        *DateSo     = nil;
686       static EOQualifier    *SeenQual   = nil;
687       
688       NSMutableArray *muids;
689       EOQualifier    *qual1, *qual2; 
690
691       if (DateSo == nil) {
692         DateSo = [[NSArray alloc] initWithObjects:
693                           [EOSortOrdering sortOrderingWithKey:@"date"
694                                            selector:[so selector]], nil];
695       }
696       if (SeenQual == nil) {
697         SeenQual = [[EOKeyValueQualifier alloc]
698                                          initWithKey:@"flags"
699                                          operatorSelector:
700                                          EOQualifierOperatorEqual
701                                          value:@"seen"];
702       }
703       muids = [[NSMutableArray alloc] initWithCapacity:255];
704
705       if (sel_eq([so selector], EOCompareAscending) ||
706           sel_eq([so selector], EOCompareCaseInsensitiveAscending)) {
707         qual1 = UnseenQual;
708         if (_unseen)
709           qual2 = nil;
710         else
711           qual2 = SeenQual;
712       }
713       else {
714         if (_unseen)
715           qual1 = nil;
716         else
717           qual1 = SeenQual;
718         
719         qual2 = UnseenQual;
720       }
721       
722       if (qual1 != nil) {
723         dict = [[self->context client] sort:DateSo qualifier:qual1
724                                        encoding:[self->context sortEncoding]];
725         
726         if (![[dict objectForKey:@"result"] boolValue]) {
727           [self logWithFormat:@"ERROR[%s](1): sort failed (sortOrderings %@, "
728                 @"qual1 %@)", __PRETTY_FUNCTION__, DateSo, qual1];
729           return nil;
730         }
731         [muids addObjectsFromArray:[dict objectForKey:@"sort"]];
732       }
733       if (qual2 != nil) {
734         dict = [[self->context client] sort:DateSo qualifier:qual2
735                                        encoding:[self->context sortEncoding]];
736         
737         if (![[dict objectForKey:@"result"] boolValue]) {
738           [self logWithFormat:@"ERROR[%s](2): sort failed (sortOrderings %@, "
739                 @"qual2 %@ ", __PRETTY_FUNCTION__, DateSo, qual2];
740           return nil;
741         }
742         [muids addObjectsFromArray:[dict objectForKey:@"sort"]];
743       }
744       uids = [muids copy];
745       [muids release]; muids = nil;
746     }
747   }
748   if (uids == nil) {
749     EOQualifier *qual;
750
751     if (![_so isNotEmpty]) {
752       static NSArray *ArrivalSO = nil;
753
754       if (ArrivalSO == nil) {
755         ArrivalSO =
756           [[NSArray alloc]
757                     initWithObjects:
758                     [EOSortOrdering sortOrderingWithKey:@"arrival"
759                                     selector:EOCompareAscending], nil];
760       }
761       _so = ArrivalSO;
762     }
763     if (_unseen) {
764       qual = UnseenQual;
765     }
766     else
767       qual = nil;
768
769     [self resetLastException];
770     
771     dict = [[self->context client] sort:_so qualifier:qual
772                                    encoding:[self->context sortEncoding]];
773     
774     if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
775       return nil;
776
777     uids = [[dict objectForKey:@"sort"] retain];
778   }
779   [pool release]; pool = nil;
780   
781   return [uids autorelease];
782 }
783
784 - (NSData *)blobForUid:(unsigned)_mUid part:(NSString *)_part {
785   /* 
786      called by NGImap4Message -contentsOfPart:
787   */
788   NSDictionary *result;
789   NSArray      *fetchResults;
790   NSString     *bodyKey;
791   NSArray      *uids, *parts;
792   
793   if (![self->context registerAsSelectedFolder:self])
794     return nil;
795
796   bodyKey = [NSString stringWithFormat:
797                         @"body[%@]", _part ? _part : (NSString *)@""];
798   uids    = [NSArray arrayWithObject:[NSNumber numberWithUnsignedInt:_mUid]];
799   parts   = [NSArray arrayWithObject:bodyKey];
800   
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];
805     return nil;
806   }
807   else if (result == nil) {
808     [self debugWithFormat:@"Note: got no result for %d/%@", _mUid, _part];
809     return nil;
810   }
811   
812   fetchResults = [result objectForKey:@"fetch"];
813   if (![fetchResults isNotEmpty])
814     [self debugWithFormat:@"found no fetch result"];
815   
816   // TODO: using 'lastObject' is certainly wrong? need to search for body
817   result = [fetchResults lastObject];
818   
819   if ((result = [result objectForKey:@"body"]) == nil)
820     [self debugWithFormat:@"found no body in fetch results: %@", fetchResults];
821   
822   return [result objectForKey:@"data"];
823 }
824
825 - (NGImap4Message *)messageForUid:(unsigned)_mUid
826   sortOrderings:(NSArray *)_so
827   onlyUnread:(BOOL)_unread
828   nextMessage:(BOOL)_next
829 {
830   NSArray      *uids, *allSortUids;
831   NSEnumerator *enumerator;
832   NSNumber     *uid, *eUid, *lastUid;
833   
834   if ([self->flags doNotSelectFolder] || self->failedFlags.select)
835     return nil;
836   
837   uid         = [NSNumber numberWithUnsignedInt:_mUid];
838   allSortUids = [self _messageIds:_so onlyUnseen:NO];
839
840   uids = _unread ? [self _messageIds:_so onlyUnseen:_unread] : (NSArray *)nil;
841   
842   enumerator  = [allSortUids objectEnumerator];
843   lastUid     = nil;
844   
845   while ((eUid = [enumerator nextObject])) {
846     if ([uid isEqual:eUid])
847       break;
848
849     if (_unread) {
850       if ([uids containsObject:eUid])
851         lastUid = eUid;
852     }
853     else
854       lastUid = eUid;
855   }
856   if (eUid == nil) {
857     [self logWithFormat:@"WARNING[%s]: Couldn`t found next/prev message "
858           @"(missing orig. message %d for sortordering %@",
859           __PRETTY_FUNCTION__, _mUid, _so];
860     return nil;
861   }
862   if (_next) {
863     if (_unread) {
864       while ((uid = [enumerator nextObject])) {
865         if ([uids containsObject:uid])
866           break;
867       }
868     }
869     else
870       uid = [enumerator nextObject];
871   }
872   else {
873     uid = lastUid;
874   }
875   if (uid == nil)
876     return nil;
877   
878   return [[[NGImap4Message alloc] initWithUid:[uid unsignedIntValue]
879                                   folder:self context:self->context]
880                                   autorelease];
881 }
882
883 /*
884   build NGImap4Messages with sorted uids
885 */
886
887 - (NSArray *)fetchSortedMessages:(NSArray *)_so {
888   NSArray           *uids, *array;
889   NSMutableArray    *marray;
890   NSAutoreleasePool *pool;
891   NSEnumerator      *enumerator;
892   NSNumber          *uid;
893   
894   if ([self->flags doNotSelectFolder] || self->failedFlags.select)
895     return nil;
896
897   if (![self->context registerAsSelectedFolder:self])
898     return nil;
899
900   pool = [[NSAutoreleasePool alloc] init];
901
902   if (![_so isNotEmpty])
903     return [self messages];
904   
905   if (!(uids = [self _messageIds:_so onlyUnseen:NO]))
906     return [self messages];
907
908   enumerator = [uids objectEnumerator];
909   marray     = [[NSMutableArray alloc] initWithCapacity:[uids count]];
910   
911   while ((uid = [enumerator nextObject])) {
912     NGImap4Message *m;
913     
914     m = [[NGImap4Message alloc] initWithUid:[uid intValue]
915                                 folder:self context:self->context];
916     if (m) [marray addObject:m];
917     [m release];
918   }
919   array = [marray shallowCopy];
920
921   [marray release]; marray = nil;
922   [pool release];   pool   = nil;
923   
924   return [array autorelease];
925 }
926
927 /*
928   fetch headers for _array in range (for use with fetchSortedMessages)
929 */
930
931 - (void)bulkFetchHeadersFor:(NSArray *)_array inRange:(NSRange)_aRange
932   withAllUnread:(BOOL)_unread
933 {
934   NSArray             *messages, *uids;
935   NSAutoreleasePool   *pool;
936   NSEnumerator        *enumerator;
937   NGImap4Message      *message;
938   NSMutableDictionary *messageMapping;
939   NSDictionary        *dict;
940   NSArray             *unreadUids;
941   
942   if ([self->flags doNotSelectFolder])
943     return;
944
945   if (_aRange.length == 0)
946     return;
947
948   if (![self->context registerAsSelectedFolder:self])
949     return;
950
951   pool = [[NSAutoreleasePool alloc] init];
952
953   if (_aRange.location >= [_array count]) {
954     return;
955   }
956   if (_aRange.location + _aRange.length > [_array count]) {
957     _aRange.length = [_array count] - _aRange.location;
958   }
959   messages   = [_array subarrayWithRange:_aRange];
960   unreadUids = nil;
961   
962   if (_unread) {
963     EOQualifier  *q;
964     NSDictionary *d;
965
966     q = [EOQualifier qualifierWithQualifierFormat:@"flags = \"unseen\""];
967     d = [[self->context client] searchWithQualifier:q];
968
969     if ([[d objectForKey:@"result"] boolValue])
970       unreadUids = [d objectForKey:@"search"];
971   }
972   enumerator     = [messages objectEnumerator];
973   messageMapping = [NSMutableDictionary dictionaryWithCapacity:
974                                         [messages count]];
975   while ((message = [enumerator nextObject]) != nil) {
976     if (![message isComplete]) {
977       [messageMapping setObject:message
978                       forKey:[NSNumber numberWithUnsignedInt:[message uid]]];
979     }
980   }
981   if ([unreadUids isNotEmpty]) {
982     enumerator = [_array objectEnumerator];
983     while ((message = [enumerator nextObject])) {
984       NSNumber *number;
985
986       number = [NSNumber numberWithUnsignedInt:[message uid]];
987
988       if ([unreadUids containsObject:number])
989         [messageMapping setObject:message forKey:number];
990     }
991   }
992   if ([messageMapping isNotEmpty]) {
993     static NSArray *sortKeys = nil;
994     
995     uids = [messageMapping allKeys];
996     
997     if (sortKeys == nil) {
998       sortKeys = [[NSArray alloc] initWithObjects:@"uid",
999                                                   @"rfc822.header",
1000                                                   @"rfc822.size", @"flags",
1001                                                   nil];
1002     }
1003     dict = [[self->context client] fetchUids:uids parts:sortKeys];
1004     if ([self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1005       [self _buildMessagesFromFetch:dict usingMessages:messageMapping];
1006
1007       if (_unread) { /* set unfetched messeges to unread */
1008         NSEnumerator   *enumerator;
1009         NGImap4Message *m;
1010
1011         enumerator = [_array objectEnumerator];
1012
1013         while ((m = [enumerator nextObject])) {
1014           NSNumber *n;
1015
1016           n = [NSNumber numberWithUnsignedInt:[m uid]];
1017
1018           if (![uids containsObject:n])
1019             [m setIsRead:YES];
1020         }
1021       }
1022     }
1023   }
1024   [pool release]; pool = nil;
1025   return;
1026 }
1027
1028 - (void)bulkFetchHeadersFor:(NSArray *)_array inRange:(NSRange)_aRange {
1029   [self bulkFetchHeadersFor:_array inRange:_aRange withAllUnread:NO];
1030 }
1031
1032 /*
1033   fetch only sorted messages in range
1034 */
1035
1036 - (NSArray *)fetchSortedMessages:(NSRange)_aRange
1037   sortOrderings:(NSArray *)_so
1038 {
1039   static NSArray *sortKeys = nil;
1040   NSDictionary      *dict;
1041   NSArray           *uids, *m;
1042   NSAutoreleasePool *pool;
1043   
1044   if ([self->flags doNotSelectFolder] || self->failedFlags.select)
1045     return nil;
1046   
1047   if (_aRange.length == 0)
1048     return [NSArray array];
1049
1050   if (![self->context registerAsSelectedFolder:self])
1051     return nil;
1052
1053   pool = [[NSAutoreleasePool alloc] init];
1054
1055   if ((uids = [self _messageIds:_so onlyUnseen:NO]) == nil)
1056     return nil;
1057   
1058   if (_aRange.location + _aRange.length > [uids count])
1059     _aRange.length = [uids count] - _aRange.location;
1060   
1061   uids = [uids subarrayWithRange:_aRange];
1062   
1063   if (sortKeys == nil) {
1064     sortKeys = [[NSArray alloc] initWithObjects:@"uid",
1065                                                 @"rfc822.header",
1066                                                 @"rfc822.size", @"flags",
1067                                                 nil];
1068   }
1069   dict = [[self->context client] fetchUids:uids parts:sortKeys];
1070
1071   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1072     return nil;
1073   
1074   m = [[self _buildMessagesFromFetch:dict] retain];
1075   [pool release];
1076   return [m autorelease];
1077 }
1078
1079 - (NSArray *)fetchMessagesFrom:(unsigned)_from to:(unsigned)_to {
1080   static NSArray *sortKeys = nil;
1081   NSAutoreleasePool   *pool;
1082   NSDictionary        *dict;
1083   NSArray             *m;
1084
1085   if ([self->flags doNotSelectFolder])
1086     return nil;
1087   
1088   if (_from == 0)
1089     _from = 1;
1090   
1091   if (![self->context registerAsSelectedFolder:self])
1092     return nil;
1093  
1094   if (_to == 0)
1095     return [NSArray array];
1096
1097   pool = [[NSAutoreleasePool alloc] init];
1098
1099   [self resetLastException];
1100   
1101   /* TODO: normalize sort-key arrays? */
1102   if (sortKeys == nil) {
1103     sortKeys = [[NSArray alloc] initWithObjects:@"uid",
1104                                                 @"rfc822.header",
1105                                                 @"rfc822.size", @"flags",
1106                                                 nil];
1107   }
1108   dict = [[self->context client] fetchFrom:_from to:_to parts:sortKeys];
1109   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1110     return nil;
1111   
1112   m = [[self _buildMessagesFromFetch:dict] retain];
1113   
1114   [pool release];
1115   return [m autorelease];
1116 }
1117
1118 - (void)initializeSubFolders {
1119   NSString     *n;
1120   NSEnumerator *folders;
1121   NSDictionary *res;
1122   id           folder, *objs;
1123   unsigned     cnt, nl;
1124   BOOL         showSubsrcFolders;
1125   NSString     *pattern;
1126   
1127   if ([self->flags doesNotSupportSubfolders])
1128     return;
1129
1130   if (!IgnoreHasNoChildrenFlag && [self->flags hasNoSubfolders])
1131     return;
1132     
1133   if (self->subFolders)
1134     [self resetSubFolders];
1135   
1136   [self resetLastException];
1137  
1138   showSubsrcFolders = [self->context showOnlySubscribedInSubFolders];
1139   
1140   pattern = [[self absoluteName] stringByAppendingString:@"/%"];
1141   res = (showSubsrcFolders)
1142     ? [[self->context client] lsub:@"" pattern:pattern]
1143     : [[self->context client] list:@"" pattern:pattern];
1144   
1145   if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1146     return;
1147   
1148   res = [res objectForKey:@"list"];
1149
1150   objs = calloc([res count] + 2, sizeof(id));
1151   {
1152     NSArray *names;
1153     names   = [res allKeys];    
1154     names   = [names sortedArrayUsingSelector:
1155                      @selector(caseInsensitiveCompare:)];
1156     folders = [names objectEnumerator];
1157   }
1158   
1159   cnt = 0;
1160   if (showSubsrcFolders) {
1161     n  = [self absoluteName];
1162     nl = [n length];
1163   }
1164   
1165   while ((folder = [folders nextObject])) {
1166     NGImap4Folder *newFolder;
1167     NSArray *f;
1168
1169     f = [res objectForKey:folder];
1170
1171     if (!ShowNonExistentFolder) {
1172       if ([f containsObject:@"nonexistent"])
1173         continue;
1174     }
1175     newFolder = [NGImap4Folder alloc]; /* to keep gcc happy */
1176     objs[cnt] = [[newFolder initWithContext:self->context
1177                             name:folder flags:f parentFolder:self]
1178                             autorelease];
1179     if (objs[cnt] == nil)
1180       break;
1181     cnt++;
1182   }
1183   if (folder == nil)
1184     self->subFolders = [[NSArray alloc] initWithObjects:objs count:cnt];
1185   
1186   if (objs) free(objs);
1187 }
1188
1189 - (BOOL)select {
1190   return [self selectImmediately:NO];
1191 }
1192
1193 - (BOOL)selectImmediately:(BOOL)_imm {
1194   NSDictionary *dict;
1195
1196   if ([self->flags doNotSelectFolder]) {
1197     [self logWithFormat:@"WARNING[%s]: try to select folder with noselect "
1198           @"flag <%@>", __PRETTY_FUNCTION__, self];
1199     return NO;
1200   }
1201   if (self->failedFlags.select)
1202     return NO;
1203   
1204   if (!_imm) {
1205     if ([[self context] isInSyncMode] && self->selectSyncState)
1206       return YES;
1207   }
1208   [self resetLastException];
1209   
1210   dict = [[self->context client] select:[self absoluteName]];
1211   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1212     self->failedFlags.select = YES;
1213     return NO;
1214   }
1215   [self->context setSelectedFolder:self];
1216
1217   ASSIGN(self->messageFlags, [dict objectForKey:@"flags"]);
1218
1219   self->isReadOnly = 
1220     [[dict objectForKey:@"access"] isEqualToString:@"READ-WRITE"]
1221     ? NoNumber : YesNumber;
1222   
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;
1226   else
1227     self->overQuota = 0;
1228
1229   [self setRecent:[dict objectForKey:@"recent"]
1230         exists:[dict objectForKey:@"exists"]];
1231   
1232   self->maxQuota          = -1;
1233   self->usedSpace         = -1;
1234   self->failedFlags.quota = NO;
1235   self->selectSyncState   = YES;
1236
1237   return YES;
1238 }
1239
1240 - (BOOL)status {
1241   NSDictionary *dict;
1242
1243   if ([self->flags doNotSelectFolder])
1244     return NO;
1245
1246   if (self->failedFlags.status)
1247     return NO;
1248   
1249   [self->context resetLastException];
1250
1251   dict = [[self->context client] status:[self absoluteName] flags:StatusFlags];
1252
1253   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1254     self->unseen             = -1;
1255     self->recent             = -1;
1256     self->exists             = -1;
1257     self->maxQuota           = -1;
1258     self->usedSpace          = -1;
1259     self->overQuota          = -1;
1260     self->failedFlags.status = YES;
1261     self->failedFlags.quota  = NO;
1262     
1263     return NO;
1264   }
1265   [self setRecent:[dict objectForKey:@"recent"]
1266         exists:[dict objectForKey:@"messages"]];
1267   self->unseen = [[dict objectForKey:@"unseen"] intValue];
1268
1269   return YES;
1270 }
1271
1272 /* actions */
1273
1274 - (void)resetFolder {
1275   // TODO: shouldn't the post happen after the expunge?
1276   [self _postResetFolderNotification];
1277   [self expunge];
1278 }
1279
1280 - (void)_resetFolder {
1281 #if USE_MESSAGE_CACHE  
1282   [self resetQualifierCache];
1283 #endif
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;  
1289 #endif
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;
1296   [self resetStatus];
1297 }
1298
1299 - (void)resetStatus {
1300   self->unseen             = -1;
1301   self->exists             = -1;
1302   self->recent             = -1;
1303   self->failedFlags.status = NO;
1304 }
1305
1306 - (void)_resetSubFolder {
1307   id ctx;
1308
1309   ctx = [self context];
1310   
1311   if ((self->parentFolder == nil) || (self == [ctx inboxFolder]))
1312     [ctx resetSpecialFolders];
1313
1314   [self->subFolders release]; self->subFolders = nil;
1315 }
1316
1317 - (void)resetSubFolders {
1318   // TODO: explain in detail what this does
1319   NSString *n;
1320   
1321   n = [self absoluteName];
1322   if ([n isNotEmpty])
1323     [self _postResetSubfolderNotification];
1324   else
1325     [self _resetSubFolder];
1326 }
1327
1328 - (BOOL)renameTo:(NSString *)_name {
1329   NSString     *n;
1330   NSDictionary *dict;
1331
1332   if ([self isReadOnly])
1333     return NO;
1334   
1335   if ((_name == nil) || ([_name length] == 0))
1336     return NO;
1337   
1338   n = [[self->name stringByDeletingLastPathComponent]
1339                    stringByAppendingPathComponent:_name];
1340
1341   [self resetLastException];
1342
1343   dict = [[self->context client] rename:[self absoluteName] to:n];
1344
1345   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1346     return NO;
1347   
1348   ASSIGNCOPY(self->name, n);
1349   [self->globalID release]; self->globalID = nil;
1350   
1351   [self resetSubFolders];
1352  
1353   dict = [[self->context client] subscribe:self->name];
1354
1355   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1356     return NO;
1357
1358   return YES;
1359 }
1360
1361 /* folder */
1362
1363 - (BOOL)deleteSubFolder:(NGImap4Folder *)_folder {
1364   if ([self isReadOnly])
1365     return NO;
1366   return _deleteSubFolder(self, _folder);;
1367 }
1368
1369 - (BOOL)copySubFolder:(NGImap4Folder *)_f to:(NGImap4Folder *)_folder {
1370   return _copySubFolder(self, _f, _folder);
1371 }
1372
1373 - (BOOL)moveSubFolder:(NGImap4Folder *)_f to:(NGImap4Folder *)_folder {
1374   if ([self isReadOnly])
1375     return NO;
1376   return _moveSubFolder(self, _f, _folder);
1377 }
1378
1379 - (BOOL)createSubFolderWithName:(NSString *)_name {
1380   if ([self isReadOnly])
1381     return NO;
1382   return _createSubFolderWithName(self, _name, YES);
1383 }
1384
1385 - (void)expunge {
1386   NSDictionary *dict;
1387   
1388   if ([self->flags doNotSelectFolder] || self->failedFlags.select)
1389     return;
1390
1391   if ([self isReadOnly])
1392     return;
1393   if (![self->context registerAsSelectedFolder:self])
1394     return;
1395       
1396   dict = [[self->context client] expunge];
1397   if (![self _checkResult:dict cmd:__PRETTY_FUNCTION__])
1398     return;
1399   
1400   [self setRecent:[dict objectForKey:@"recent"]
1401         exists:[dict objectForKey:@"exists"]];
1402 }
1403
1404 - (BOOL)addFlag:(NSString *)_flag toMessages:(NSArray *)_messages {
1405   return [self flag:_flag toMessages:_messages add:YesNumber];
1406 }
1407
1408 - (BOOL)removeFlag:(NSString *)_flag fromMessages:(NSArray *)_messages {
1409   return [self flag:_flag toMessages:_messages add:NoNumber];
1410 }
1411
1412 - (BOOL)flag:(NSString *)_flag toMessages:(NSArray *)_messages
1413   add:(NSNumber *)_add
1414 {
1415   NSEnumerator   *enumerator;
1416   NGImap4Message *message;
1417   NSDictionary   *obj;
1418   BOOL           add;
1419   NSArray        *flagArray;
1420   
1421   add = [_add boolValue];   
1422
1423   if ([self->flags doNotSelectFolder])
1424     return NO;
1425
1426   if (_flag == nil) {
1427     [self logWithFormat:@"WARNING[%s]: try to set an empty flag",
1428           __PRETTY_FUNCTION__];
1429     return NO;
1430   }
1431   if ([self isReadOnly])
1432     return NO;
1433   
1434   if (![self->context registerAsSelectedFolder:self])
1435     return NO;
1436
1437   if (![self _testMessages:_messages operation:@"store"])
1438     return NO;
1439
1440   [self resetLastException];
1441   
1442   enumerator = [[self _getMsnRanges:_messages] objectEnumerator];
1443   if (enumerator == nil) {
1444     [self resetStatus];
1445     [self->context removeSelectedFolder:self];
1446     return NO;
1447   }
1448   
1449   flagArray = [_flag isNotNull] ? [NSArray arrayWithObject:_flag] : nil;
1450   while ((obj = [enumerator nextObject])) {
1451     NSDictionary *res;
1452     int objEnd, objStart;
1453     
1454     if ((objEnd = [[obj objectForKey:@"end"] unsignedIntValue]) <= 0)
1455       continue;
1456     
1457     objStart = [[obj objectForKey:@"start"] unsignedIntValue];
1458     res = [[self->context client]
1459                           storeFrom:objStart to:objEnd
1460                           add:_add flags:flagArray];
1461
1462     if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1463       break;
1464   }
1465   if (obj)
1466     return NO;
1467
1468   enumerator = [_messages objectEnumerator];
1469   while ((message = [enumerator nextObject])) {
1470     if (add)
1471       [message addFlag:_flag];
1472     else
1473       [message removeFlag:_flag];
1474   }
1475   [self resetStatus];
1476   return YES;
1477 }
1478
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;
1484 #endif
1485
1486   if ([self->flags doNotSelectFolder])
1487     return NO;
1488   
1489   if (_flag == nil)
1490     return NO;
1491
1492   if ([self isReadOnly])
1493     return NO;
1494
1495   if (![self->context registerAsSelectedFolder:self])
1496     return NO;
1497
1498   
1499   if ([self exists] > 0) {
1500     NSDictionary *res;
1501     
1502     [self resetLastException];
1503     
1504     res = [[self->context client] storeFrom:0 to:[self exists]
1505                                   add:_add
1506                                   flags:[NSArray arrayWithObject:_flag]];
1507
1508     if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1509       return NO;
1510     
1511 #if USE_MESSAGE_CACHE
1512     enumerator = [self->messages objectEnumerator];
1513     while ((m = [enumerator nextObject])) {
1514       (add)
1515         ? [m addFlag:_flag]
1516         : [m removeFlag:_flag];
1517     }
1518 #endif
1519   }
1520   return YES;
1521 }
1522
1523 - (BOOL)deleteAllMessages {
1524   if ([self isReadOnly])
1525     return NO;
1526
1527   if ([self flagToAllMessages:@"Deleted" add:YesNumber]) {
1528     [self resetFolder];
1529     return YES;
1530   }
1531   return NO;
1532 }
1533
1534 - (BOOL)deleteMessages:(NSArray *)_messages {
1535   if ([self isReadOnly])
1536     return NO;
1537
1538   if ([self addFlag:@"Deleted" toMessages:_messages]) {
1539     [self resetFolder];
1540     return YES;
1541   }
1542   return NO;
1543 }
1544
1545 - (BOOL)moveMessages:(NSArray *)_messages toFolder:(NGImap4Folder *)_folder {
1546   if ([self isReadOnly])
1547     return NO;
1548
1549   if (_folder != nil) {
1550     if ([self copyMessages:_messages toFolder:_folder]) {
1551       return [self deleteMessages:_messages];
1552     }
1553   }
1554   return NO;
1555 }
1556
1557 - (BOOL)copyMessages:(NSArray *)_messages toFolder:(NGImap4Folder *)_folder {
1558   NSEnumerator *enumerator;
1559   NSDictionary *obj;
1560   NSString     *folderName;
1561
1562   if ([self->flags doNotSelectFolder])
1563     return NO;
1564   
1565   folderName = [_folder absoluteName];  
1566
1567   if (_folder == nil) {
1568     [self logWithFormat:@"WARNING[%s]: try to copy to nil folder",
1569           __PRETTY_FUNCTION__];
1570     return NO;
1571   }
1572   [self resetLastException];
1573   
1574   if ([_folder isReadOnly]) {
1575     NGImap4ResponseException *exc;
1576
1577     [self logWithFormat:@"WARNING[%s]: try to copy to readonly folder %@",
1578           __PRETTY_FUNCTION__, _folder];
1579
1580     exc = [[NGImap4ResponseException alloc] initWithFormat:
1581                                             @"copy to read only folder"];
1582     [self->context setLastException:exc];
1583     [exc release]; exc = nil;
1584     return NO;
1585   }
1586   if (![self->context registerAsSelectedFolder:self])
1587     return NO;
1588   
1589   if (![self _testMessages:_messages operation:@"copy"])
1590     return NO;
1591   
1592   enumerator = [[self _getMsnRanges:_messages] objectEnumerator];
1593   if (enumerator == nil) {
1594     [self resetStatus];
1595     [self->context removeSelectedFolder:self];
1596     return NO;
1597   }
1598   
1599   [self resetLastException];
1600   if (![self->context registerAsSelectedFolder:self])
1601     return NO;
1602   
1603   while ((obj = [enumerator nextObject])) {
1604     int objEnd;
1605     
1606     objEnd = [[obj objectForKey:@"end"] unsignedIntValue];
1607
1608     if (objEnd > 0) {
1609       NSDictionary *res;
1610       unsigned int start;
1611       
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__])
1616         break;
1617     }
1618   }
1619   [_folder resetFolder];
1620   return (obj == nil) ? YES : NO;
1621 }
1622
1623 - (BOOL)appendMessage:(NSData *)_msg {
1624   if ([self isReadOnly])
1625     return NO;
1626
1627   if (_msg != nil) {
1628     NSDictionary *dict;
1629
1630     dict = [[self->context client]
1631                            append:_msg toFolder:[self absoluteName]
1632                            withFlags:[NSArray arrayWithObject:@"seen"]];
1633
1634     if ([self _checkResult:dict cmd:__PRETTY_FUNCTION__]) {
1635       [self resetFolder];
1636       return YES;
1637     }
1638   }
1639   return NO;
1640 }
1641
1642 #if USE_MESSAGE_CACHE
1643 - (void)resetQualifierCache {
1644   self->cacheIdx = 0;
1645   [self->qualifierCache removeAllObjects];
1646 }
1647 #endif
1648
1649 /* notifications */
1650
1651 - (void)setRecent:(NSNumber *)_rec exists:(NSNumber *)_exists {
1652 #if USE_MESSAGE_CACHE  
1653   BOOL resetQualifier = NO;
1654   if (_rec != nil) {
1655     int tmp = [_rec intValue];
1656       if (self->recent != tmp) {
1657         self->recent   = tmp;
1658         resetQualifier = YES;
1659       }
1660   }
1661   if (_exists != nil) {
1662     int tmp = [_exists intValue];
1663     if (self->exists != tmp) {
1664       self->exists = tmp;
1665       resetQualifier = YES;
1666     }
1667   }
1668   if (resetQualifier) 
1669     [self resetQualifierCache];
1670 #else
1671   {
1672     if (_exists != nil) {
1673       int      e;
1674
1675       e = [_exists intValue];
1676
1677       if (e == 0) {
1678         self->exists = 0;
1679         self->recent = 0;
1680         self->unseen = 0;
1681       }
1682       else {
1683         int      r;
1684     
1685         r  = [_rec intValue];
1686
1687         if ((e != self->exists) || (r != self->recent)) {
1688           self->exists = e;
1689           self->recent = r;
1690           self->unseen = -1;
1691         }
1692       }
1693     }
1694   }
1695 #endif  
1696 }
1697
1698 - (void)processResponse:(NSDictionary *)_dict {
1699 #if USE_MESSAGE_CACHE  
1700   id exp = [_dict objectForKey:@"expunge"];
1701 #endif
1702
1703   [self setRecent:[_dict objectForKey:@"recent"]
1704         exists:[_dict objectForKey:@"exists"]];
1705
1706 #if USE_MESSAGE_CACHE  
1707   if ((exp != nil) && ([exp count] > 0) && ([self->messages count] > 0)) {
1708     NSEnumerator *enumerator;
1709     id           obj;
1710     
1711     enumerator = [exp objectEnumerator];
1712     while ((obj = [enumerator nextObject])) {
1713       int n = [obj intValue] - 1;
1714
1715       [self->messages removeObjectAtIndex:n];
1716     }
1717     [self resetQualifierCache];
1718   }
1719 #endif  
1720 }
1721
1722 - (BOOL)isInTrash {
1723   id<NGImap4Folder> f, trash;
1724
1725   trash = [self->context trashFolder];
1726
1727   if (trash == nil) {
1728     [self logWithFormat:@"WARNING[%s]: No trash folder was set",
1729           __PRETTY_FUNCTION__];
1730     return NO;
1731   }
1732
1733   for (f = self; f; f = [f parentFolder]) {
1734     if ([f isEqual:trash])
1735       return YES;
1736   }
1737   return NO;
1738 }
1739
1740 - (void)resetSync {
1741   NSEnumerator *enumerator;
1742   id           folder;
1743
1744   self->selectSyncState = NO;
1745   
1746   if (self->subFolders == nil)
1747     return;
1748   
1749   enumerator = [[self subFolders] objectEnumerator];
1750   while ((folder = [enumerator nextObject]))
1751     [folder resetSync];
1752 }
1753
1754 - (NSURL *)url {
1755   NSString *p;
1756   NSURL *base;
1757   
1758   if (self->url != nil)
1759     return self->url;
1760   
1761   if ((base = [self->context url]) == nil) {
1762     [self logWithFormat:@"ERROR: got no URL for context: %@", self->context];
1763     return nil;
1764   }
1765   
1766   if ((p = [self absoluteName]) == nil)
1767     return nil;
1768   
1769   if (![p hasPrefix:@"/"]) p = [@"/" stringByAppendingString:p];
1770   self->url = [[NSURL alloc]
1771                 initWithScheme:[base scheme] host:[base host] path:p];
1772   return self->url;
1773 }
1774
1775 - (EOGlobalID *)serverGlobalID {
1776   return [self->context serverGlobalID];
1777 }
1778 - (EOGlobalID *)globalID {
1779   if (self->globalID)
1780     return self->globalID;
1781
1782   self->globalID = [[NGImap4FolderGlobalID alloc] initWithServerGlobalID:
1783                                                     [self serverGlobalID]
1784                                                   andAbsoluteName:
1785                                                     [self absoluteName]];
1786   return self->globalID;
1787 }
1788
1789 /* quota information */
1790
1791 - (void)quota {
1792   NSString     *n;
1793   NSDictionary *quota;
1794
1795   if (self->failedFlags.quota)
1796     return;
1797
1798   if (![self->context canQuota]) {
1799     [self logWithFormat:@"WARNING[%s] call quota but capability contains"
1800           @" no quota string", __PRETTY_FUNCTION__];
1801     return;
1802   }
1803   n     = [self absoluteName];
1804   [self resetLastException];
1805
1806   if ([self->flags doNotSelectFolder])
1807     return;
1808   
1809   quota = [[self->context client] getQuotaRoot:n];
1810
1811   if (![self _checkResult:quota cmd:__PRETTY_FUNCTION__]) {
1812     self->failedFlags.quota = YES;
1813     return;
1814   }
1815   
1816   quota = [quota objectForKey:@"quotas"];
1817   quota = [quota objectForKey:n];
1818   
1819   self->maxQuota  = [[quota objectForKey:@"maxQuota"]  intValue];
1820   self->usedSpace = [[quota objectForKey:@"usedSpace"] intValue];
1821 }
1822
1823
1824 - (BOOL)_testMessages:(NSArray *)_messages operation:(NSString *)_operation {
1825   NSEnumerator *enumerator;
1826   id           obj;
1827   
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];
1834       return NO;
1835     }
1836   }
1837   return YES;
1838 }
1839
1840 - (NSArray *)_getMsnRanges:(NSArray *)_messages {
1841   // TODO: might split up? document!
1842   static NSArray      *UidKey     = nil;
1843   
1844   NSArray             *result;
1845   NSMutableDictionary *map;
1846   NSMutableArray      *msn;
1847   NSEnumerator        *enumerator;  
1848   NSDictionary        *obj;
1849   NSAutoreleasePool   *pool;
1850   NGImap4Message      *message;
1851   
1852   if ([self exists] == 0)
1853     return [NSArray array];
1854
1855   pool = [[NSAutoreleasePool alloc] init];
1856   
1857   if (UidKey == nil) {
1858     id objs = nil;
1859
1860     objs   = @"uid";
1861     UidKey = [[NSArray alloc] initWithObjects:&objs count:1];
1862   }
1863
1864   [self resetLastException];
1865   
1866   if (![self->context registerAsSelectedFolder:self])
1867     return nil;
1868
1869   if ([_messages count] > [self->msn2UidCache count]) {
1870     [self->msn2UidCache release]; 
1871     self->msn2UidCache = nil;
1872   }
1873   
1874   if (!self->msn2UidCache) {
1875     NSDictionary *res;
1876
1877     res = [[self->context client] fetchFrom:1 to:[self exists] parts:UidKey];
1878
1879     if (![self _checkResult:res cmd:__PRETTY_FUNCTION__])
1880       return nil;
1881     
1882     self->msn2UidCache = [[res objectForKey:@"fetch"] retain];
1883   }
1884   map = [[NSMutableDictionary alloc] initWithCapacity:
1885                                      [self->msn2UidCache count]];
1886   enumerator = [self->msn2UidCache objectEnumerator];
1887   
1888   while ((obj = [enumerator nextObject]))
1889     [map setObject:[obj objectForKey:@"msn"] forKey:[obj objectForKey:@"uid"]];
1890   
1891   msn = [[NSMutableArray alloc] initWithCapacity:[_messages count]];
1892   enumerator = [_messages objectEnumerator];
1893   while ((message = [enumerator nextObject])) {
1894     id m;
1895     
1896     m = [map objectForKey:[NSNumber numberWithUnsignedInt:[message uid]]];
1897     
1898     if (m == nil) {
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];
1903       [msn  release];
1904       [map  release];
1905       [pool release];
1906       return nil;
1907     }
1908     [msn addObject:m];
1909   }
1910   [map release]; map = nil;
1911   
1912   result = [self _calculateSequences:msn count:-1];
1913   
1914   result = [result retain];
1915   [msn release]; msn = nil;
1916   [pool release];
1917   return [result autorelease];
1918 }
1919
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;
1926   id                  obj, buffer;
1927   int                 cntMsgs;
1928
1929   pool = [[NSAutoreleasePool alloc] init];
1930
1931   if (_cnt == -1)
1932     _cnt = [_numbers count];
1933   
1934   [_numbers sortUsingSelector:@selector(compare:)];
1935   
1936   ranges     = [NSMutableArray arrayWithCapacity:[_numbers count]];
1937   enumerator = [_numbers objectEnumerator];
1938   buffer     = [NSNumber numberWithInt:0];
1939   range      = nil;
1940   cntMsgs    = 0;
1941   while (((obj = [enumerator nextObject])) && (cntMsgs < _cnt)) {
1942     cntMsgs++;
1943     if (range == nil) {
1944       range = [NSMutableDictionary dictionaryWithCapacity:2];
1945       [range setObject:buffer forKey:@"start"];
1946     }
1947     
1948     if ([obj intValue] != [buffer intValue] + 1) {
1949       NSDictionary *ir;
1950       
1951       [range setObject:buffer forKey:@"end"];
1952       ir = [range copy];
1953       [ranges addObject:ir];
1954       [ir release];
1955       range = nil;
1956     }
1957     buffer = obj;
1958   }
1959   if (range != nil) {
1960     [range setObject:buffer forKey:@"end"];
1961     [ranges addObject:range];
1962   }
1963   else {
1964     NSDictionary *d;
1965     
1966     d = [[NSDictionary alloc] initWithObjectsAndKeys:
1967                                 buffer, @"start", buffer, @"end", nil];
1968     [ranges addObject:d];
1969     [d release];
1970   }
1971   range = [ranges objectAtIndex:0];
1972   if ([[range objectForKey:@"end"] intValue] == 0)
1973     [ranges removeObjectAtIndex:0];
1974   
1975   obj = [ranges copy];
1976   [pool release];
1977   return [obj autorelease];
1978 }
1979
1980 - (void)clearParentFolder {
1981   self->parentFolder = nil;
1982 }
1983
1984 /* message factory */
1985
1986 - (id)messageWithUid:(unsigned int)_uid {
1987   return [[[NGImap4Message alloc] 
1988                            initWithUid:_uid folder:self context:[self context]]
1989                            autorelease];
1990 }
1991
1992 /* message registry */
1993
1994 - (NGImap4FolderMailRegistry *)mailRegistry {
1995   return self->mailRegistry;
1996 }
1997
1998 /* debugging */
1999
2000 - (BOOL)isDebuggingEnabled {
2001   return ImapDebugEnabled;
2002 }
2003
2004 /* description */
2005
2006 - (NSString *)description {
2007   NSMutableString *ms;
2008   NSString        *tmp;
2009
2010   ms = [NSMutableString stringWithCapacity:64];
2011
2012   [ms appendFormat:@"<0x%p[%@]:", self, NSStringFromClass([self class])];
2013   
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];
2020   
2021   [ms appendString:@">"];
2022
2023   return ms;
2024 }
2025
2026 @end /* NGImap4Folder */
2027
2028 #if 0
2029 @implementation NSData(xxxx)
2030 - (NSString *)description {
2031   return [NSString stringWithFormat:@"NSData len: %d",
2032                    [self length]];
2033 }
2034 @end
2035 #endif