2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
25 This component represent a list of mails and is attached to an SOGoMailFolder
29 #import <Foundation/NSCalendarDate.h>
30 #import <Foundation/NSDictionary.h>
31 #import <Foundation/NSValue.h>
32 #import <NGObjWeb/WOResponse.h>
33 #import <NGObjWeb/WORequest.h>
34 #import <NGObjWeb/SoObject+SoDAV.h>
35 #import <NGObjWeb/NSException+HTTP.h>
36 #import <NGExtensions/NSNull+misc.h>
37 #import <NGExtensions/NSString+misc.h>
39 #import <SoObjects/Mailer/SOGoMailFolder.h>
40 #import <SoObjects/Mailer/SOGoMailObject.h>
41 #import <SoObjects/SOGo/SOGoDateFormatter.h>
42 #import <SoObjects/SOGo/SOGoUser.h>
44 #import "UIxMailListView.h"
46 #define messagesPerPage 50
47 static int attachmentFlagSize = 8096;
49 @implementation UIxMailListView
53 [self->qualifier release];
54 [self->sortedUIDs release];
55 [self->messages release];
56 [self->message release];
57 [dateFormatter release];
58 [userTimeZone release];
66 [self->qualifier release]; self->qualifier = nil;
67 [self->sortedUIDs release]; self->sortedUIDs = nil;
68 [self->messages release]; self->messages = nil;
69 [self->message release]; self->message = nil;
75 - (void)setMessage:(id)_msg
77 ASSIGN(self->message, _msg);
85 - (NSString *) messageDate
87 NSCalendarDate *messageDate;
89 messageDate = [[message objectForKey: @"envelope"] date];
90 [messageDate setTimeZone: userTimeZone];
92 return [dateFormatter formattedDateAndTime: messageDate];
95 - (void) setQualifier: (EOQualifier *) _msg
97 ASSIGN(self->qualifier, _msg);
100 - (EOQualifier *) qualifier
102 return self->qualifier;
105 - (BOOL) showToAddress
109 ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
110 return [ftype isEqual:@"IPF.Sent"];
115 - (NSString *) objectTitle
117 return [[self clientObject] nameInContainer];
120 - (NSString *) panelTitle
124 s = [self labelForKey:@"View Mail Folder"];
125 s = [s stringByAppendingString:@": "];
126 s = [s stringByAppendingString:[self objectTitle]];
130 /* derived accessors */
132 - (BOOL) isMessageDeleted
136 flags = [[self message] valueForKey:@"flags"];
137 return [flags containsObject:@"deleted"];
140 - (BOOL) isMessageRead
144 flags = [[self message] valueForKey:@"flags"];
145 return [flags containsObject:@"seen"];
147 - (NSString *) messageUidString
149 return [[[self message] valueForKey:@"uid"] stringValue];
152 - (NSString *) messageRowStyleClass
154 return [self isMessageDeleted]
155 ? @"mailer_listcell_deleted"
156 : @"mailer_listcell_regular";
159 - (NSString *) messageSubjectCellStyleClass
161 return ([self isMessageRead]
162 ? @"mailer_readmailsubject"
163 : @"mailer_unreadmailsubject");
166 - (BOOL) hasMessageAttachment
168 /* we detect attachments by size ... */
171 size = [[[self message] valueForKey:@"size"] intValue];
172 return size > attachmentFlagSize;
175 /* fetching messages */
177 - (NSArray *) fetchKeys
179 /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
180 static NSArray *keys = nil;
182 keys = [[NSArray alloc] initWithObjects:
183 @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
188 - (NSString *) defaultSortKey
193 - (NSString *) imap4SortKey
197 sort = [[[self context] request] formValueForKey:@"sort"];
199 if ([sort length] == 0)
200 sort = [self defaultSortKey];
202 return [sort uppercaseString];
205 - (BOOL) isSortedDescending
209 desc = [[[self context] request] formValueForKey:@"desc"];
216 - (NSString *) imap4SortOrdering
220 sort = [self imap4SortKey];
222 if ([self isSortedDescending])
223 sort = [@"REVERSE " stringByAppendingString: sort];
228 - (NSRange) fetchRange
230 if (self->firstMessageNumber == 0)
231 return NSMakeRange(0, messagesPerPage);
232 return NSMakeRange(self->firstMessageNumber - 1, messagesPerPage);
235 - (NSArray *) sortedUIDs
240 = [[self clientObject] fetchUIDsMatchingQualifier: [self qualifier]
241 sortOrdering: [self imap4SortOrdering]];
245 return self->sortedUIDs;
248 - (unsigned int) totalMessageCount
250 return [self->sortedUIDs count];
253 - (BOOL) showsAllMessages
255 return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
258 - (NSRange) fetchBlock
264 r = [self fetchRange];
265 uids = [self sortedUIDs];
267 /* only need to restrict if we have a lot */
268 if ((len = [uids count]) <= r.length) {
274 if (len < r.location) {
275 // TODO: CHECK CONDITION (< vs <=)
276 /* out of range, recover at first block */
281 if (r.location + r.length > len)
282 r.length = len - r.location;
286 - (unsigned int) firstMessageNumber
288 return [self fetchBlock].location + 1;
291 - (unsigned int) lastMessageNumber
295 r = [self fetchBlock];
296 return r.location + r.length;
301 return [self fetchBlock].location == 0 ? NO : YES;
306 NSRange r = [self fetchBlock];
307 return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
310 - (unsigned int) nextFirstMessageNumber
312 return [self firstMessageNumber] + [self fetchRange].length;
315 - (unsigned int) prevFirstMessageNumber
320 idx = [self firstMessageNumber];
321 r = [self fetchRange];
323 return (idx - r.length);
327 - (NSArray *) messages
335 if (self->messages != nil)
336 return self->messages;
338 user = [context activeUser];
340 dateFormatter = [user dateFormatterInContext: context];
342 ASSIGN (userTimeZone, [user timeZone]);
344 r = [self fetchBlock];
345 uids = [self sortedUIDs];
346 if ((len = [uids count]) > r.length)
347 /* only need to restrict if we have a lot */
348 uids = [uids subarrayWithRange:r];
350 msgs = [[self clientObject] fetchUIDs:uids parts:[self fetchKeys]];
351 self->messages = [[msgs valueForKey:@"fetch"] retain];
352 return self->messages;
357 - (NSString *) messageViewTarget
359 return [NSString stringWithFormat: @"SOGo_msg_%@",
360 [self messageUidString]];
363 - (NSString *) messageViewURL
365 // TODO: noframe only when view-target is empty
366 // TODO: markread only if the message is unread
369 s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
370 if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
373 - (NSString *) markReadURL
375 return [@"markMessageRead?uid=" stringByAppendingString:
376 [self messageUidString]];
378 - (NSString *) markUnreadURL
380 return [@"markMessageUnread?uid=" stringByAppendingString:
381 [self messageUidString]];
386 - (NSString *)msgRowID
388 return [@"row_" stringByAppendingString:[self messageUidString]];
391 - (NSString *)msgDivID
393 return [@"div_" stringByAppendingString:[self messageUidString]];
396 - (NSString *)msgIconReadImgID
398 return [@"readdiv_" stringByAppendingString:[self messageUidString]];
401 - (NSString *)msgIconUnreadImgID
403 return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
406 /* error redirects */
408 - (id) redirectToViewWithError: (id) _error
410 // TODO: DUP in UIxMailAccountView
411 // TODO: improve, localize
412 // TODO: there is a bug in the treeview which preserves the current URL for
413 // the active object (displaying the error again)
416 if (![_error isNotNull])
417 return [self redirectToLocation:@"view"];
419 if ([_error isKindOfClass:[NSException class]])
420 _error = [_error reason];
421 else if ([_error isKindOfClass:[NSString class]])
422 _error = [_error stringValue];
424 url = [_error stringByEscapingURL];
425 url = [@"view?error=" stringByAppendingString:url];
426 return [self redirectToLocation:url];
431 - (SOGoMailObject *) lookupActiveMessage
435 if ((uid = [[[self context] request] formValueForKey:@"uid"]) == nil)
438 return [[self clientObject] lookupName:uid inContext:[self context]
444 - (BOOL) isJavaScriptRequest
446 return [[[[self context] request] formValueForKey:@"jsonly"] boolValue];
453 r = [[self context] response];
454 [r setStatus:200 /* OK */];
458 - (int) firstMessageOfPageFor: (int) messageNbr
460 NSArray *messageNbrs;
464 messageNbrs = [self sortedUIDs];
466 = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
468 firstMessage = ((int) (nbrInArray / messagesPerPage)
469 * messagesPerPage) + 1;
479 NSString *specificMessage;
481 request = [[self context] request];
483 [[self clientObject] flushMailCaches];
485 specificMessage = [request formValueForKey: @"pageforuid"];
486 self->firstMessageNumber
488 ? [self firstMessageOfPageFor: [specificMessage intValue]]
489 : [[request formValueForKey:@"idx"] intValue]);
496 return [self defaultAction];
499 - (id) markMessageUnreadAction
503 if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
504 // TODO: improve error handling
507 if ([self isJavaScriptRequest])
508 return [self javaScriptOK];
510 return [self redirectToLocation:@"view"];
513 - (id) markMessageReadAction
517 if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
518 // TODO: improve error handling
521 if ([self isJavaScriptRequest])
522 return [self javaScriptOK];
524 return [self redirectToLocation:@"view"];
529 // TODO: we might want to flush the caches?
532 if ((client = [self clientObject]) == nil) {
533 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
534 reason:@"did not find mail folder"];
537 if (![client respondsToSelector:@selector(flushMailCaches) ])
539 return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
541 @"invalid client object (does not support flush)"];
544 [client flushMailCaches];
546 return [self redirectToLocation:@"view"];
551 // TODO: we might want to flush the caches?
555 if ((client = [self clientObject]) == nil) {
556 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
557 reason:@"did not find mail folder"];
560 if ((error = [[self clientObject] expunge]) != nil)
563 if ([client respondsToSelector:@selector(flushMailCaches)])
564 [client flushMailCaches];
565 return [self redirectToLocation:@"view"];
570 /* UIxMailListView */