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