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 #define messagesPerPage 50
32 #include <SoObjects/Mailer/SOGoMailFolder.h>
33 #include <SoObjects/Mailer/SOGoMailObject.h>
34 #include <NGObjWeb/SoObject+SoDAV.h>
36 #import "UIxMailListView.h"
38 static int attachmentFlagSize = 8096;
40 @implementation UIxMailListView
44 [self->qualifier release];
45 [self->sortedUIDs release];
46 [self->messages release];
47 [self->message release];
55 [self->qualifier release]; self->qualifier = nil;
56 [self->sortedUIDs release]; self->sortedUIDs = nil;
57 [self->messages release]; self->messages = nil;
58 [self->message release]; self->message = nil;
64 - (void)setMessage:(id)_msg
66 ASSIGN(self->message, _msg);
74 - (void) setQualifier: (EOQualifier *) _msg
76 ASSIGN(self->qualifier, _msg);
79 - (EOQualifier *) qualifier
81 return self->qualifier;
84 - (BOOL) showToAddress
88 ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
89 return [ftype isEqual:@"IPF.Sent"];
94 - (NSString *) objectTitle
96 return [[self clientObject] nameInContainer];
99 - (NSString *) panelTitle
103 s = [self labelForKey:@"View Mail Folder"];
104 s = [s stringByAppendingString:@": "];
105 s = [s stringByAppendingString:[self objectTitle]];
109 /* derived accessors */
111 - (BOOL) isMessageDeleted
115 flags = [[self message] valueForKey:@"flags"];
116 return [flags containsObject:@"deleted"];
119 - (BOOL) isMessageRead
123 flags = [[self message] valueForKey:@"flags"];
124 return [flags containsObject:@"seen"];
126 - (NSString *) messageUidString
128 return [[[self message] valueForKey:@"uid"] stringValue];
131 - (NSString *) messageCellStyleClass
133 return [self isMessageDeleted]
134 ? @"mailer_listcell_deleted"
135 : @"mailer_listcell_regular";
138 - (NSString *) messageSubjectCellStyleClass
140 return [NSString stringWithFormat: @"%@ %@",
141 [self messageCellStyleClass],
142 ([self isMessageRead]
143 ? @"mailer_readmailsubject"
144 : @"mailer_unreadmailsubject")];
147 - (BOOL) hasMessageAttachment
149 /* we detect attachments by size ... */
152 size = [[[self message] valueForKey:@"size"] intValue];
153 return size > attachmentFlagSize;
156 /* fetching messages */
158 - (NSArray *) fetchKeys
160 /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
161 static NSArray *keys = nil;
163 keys = [[NSArray alloc] initWithObjects:
164 @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
169 - (NSString *) defaultSortKey
174 - (NSString *) imap4SortKey
178 sort = [[[self context] request] formValueForKey:@"sort"];
180 if ([sort length] == 0)
181 sort = [self defaultSortKey];
183 return [sort uppercaseString];
186 - (BOOL) isSortedDescending
190 desc = [[[self context] request] formValueForKey:@"desc"];
197 - (NSString *) imap4SortOrdering
201 sort = [self imap4SortKey];
203 if ([self isSortedDescending])
204 sort = [@"REVERSE " stringByAppendingString: sort];
209 - (NSRange) fetchRange
211 if (self->firstMessageNumber == 0)
212 return NSMakeRange(0, messagesPerPage);
213 return NSMakeRange(self->firstMessageNumber - 1, messagesPerPage);
216 - (NSArray *) sortedUIDs
221 = [[self clientObject] fetchUIDsMatchingQualifier: [self qualifier]
222 sortOrdering: [self imap4SortOrdering]];
226 return self->sortedUIDs;
229 - (unsigned int) totalMessageCount
231 return [self->sortedUIDs count];
234 - (BOOL) showsAllMessages
236 return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
239 - (NSRange) fetchBlock
245 r = [self fetchRange];
246 uids = [self sortedUIDs];
248 /* only need to restrict if we have a lot */
249 if ((len = [uids count]) <= r.length) {
255 if (len < r.location) {
256 // TODO: CHECK CONDITION (< vs <=)
257 /* out of range, recover at first block */
262 if (r.location + r.length > len)
263 r.length = len - r.location;
267 - (unsigned int) firstMessageNumber
269 return [self fetchBlock].location + 1;
272 - (unsigned int) lastMessageNumber
276 r = [self fetchBlock];
277 return r.location + r.length;
282 return [self fetchBlock].location == 0 ? NO : YES;
287 NSRange r = [self fetchBlock];
288 return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
291 - (unsigned int) nextFirstMessageNumber
293 return [self firstMessageNumber] + [self fetchRange].length;
296 - (unsigned int) prevFirstMessageNumber
301 idx = [self firstMessageNumber];
302 r = [self fetchRange];
304 return (idx - r.length);
308 - (NSArray *) messages
315 if (self->messages != nil)
316 return self->messages;
318 r = [self fetchBlock];
319 uids = [self sortedUIDs];
320 if ((len = [uids count]) > r.length)
321 /* only need to restrict if we have a lot */
322 uids = [uids subarrayWithRange:r];
324 msgs = [[self clientObject] fetchUIDs:uids parts:[self fetchKeys]];
325 self->messages = [[msgs valueForKey:@"fetch"] retain];
326 return self->messages;
331 - (NSString *) messageViewTarget
333 return [NSString stringWithFormat: @"SOGo_msg_%@",
334 [self messageUidString]];
337 - (NSString *) messageViewURL
339 // TODO: noframe only when view-target is empty
340 // TODO: markread only if the message is unread
343 s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
344 if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
347 - (NSString *) markReadURL
349 return [@"markMessageRead?uid=" stringByAppendingString:
350 [self messageUidString]];
352 - (NSString *) markUnreadURL
354 return [@"markMessageUnread?uid=" stringByAppendingString:
355 [self messageUidString]];
360 - (NSString *)msgRowID
362 return [@"row_" stringByAppendingString:[self messageUidString]];
365 - (NSString *)msgDivID
367 return [@"div_" stringByAppendingString:[self messageUidString]];
370 - (NSString *)msgIconReadImgID
372 return [@"readdiv_" stringByAppendingString:[self messageUidString]];
375 - (NSString *)msgIconUnreadImgID
377 return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
380 /* error redirects */
382 - (id) redirectToViewWithError: (id) _error
384 // TODO: DUP in UIxMailAccountView
385 // TODO: improve, localize
386 // TODO: there is a bug in the treeview which preserves the current URL for
387 // the active object (displaying the error again)
390 if (![_error isNotNull])
391 return [self redirectToLocation:@"view"];
393 if ([_error isKindOfClass:[NSException class]])
394 _error = [_error reason];
395 else if ([_error isKindOfClass:[NSString class]])
396 _error = [_error stringValue];
398 url = [_error stringByEscapingURL];
399 url = [@"view?error=" stringByAppendingString:url];
400 return [self redirectToLocation:url];
405 - (SOGoMailObject *) lookupActiveMessage
409 if ((uid = [[[self context] request] formValueForKey:@"uid"]) == nil)
412 return [[self clientObject] lookupName:uid inContext:[self context]
418 - (BOOL) isJavaScriptRequest
420 return [[[[self context] request] formValueForKey:@"jsonly"] boolValue];
427 r = [[self context] response];
428 [r setStatus:200 /* OK */];
432 - (int) firstMessageOfPageFor: (int) messageNbr
434 NSArray *messageNbrs;
438 messageNbrs = [self sortedUIDs];
440 = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
442 firstMessage = ((int) (nbrInArray / messagesPerPage)
443 * messagesPerPage) + 1;
453 NSString *specificMessage;
455 request = [[self context] request];
457 [[self clientObject] flushMailCaches];
459 specificMessage = [request formValueForKey: @"pageforuid"];
460 self->firstMessageNumber
462 ? [self firstMessageOfPageFor: [specificMessage intValue]]
463 : [[request formValueForKey:@"idx"] intValue]);
470 return [self defaultAction];
473 - (id) markMessageUnreadAction
477 if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
478 // TODO: improve error handling
481 if ([self isJavaScriptRequest])
482 return [self javaScriptOK];
484 return [self redirectToLocation:@"view"];
487 - (id) markMessageReadAction
491 if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
492 // TODO: improve error handling
495 if ([self isJavaScriptRequest])
496 return [self javaScriptOK];
498 return [self redirectToLocation:@"view"];
503 // TODO: we might want to flush the caches?
506 if ((client = [self clientObject]) == nil) {
507 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
508 reason:@"did not find mail folder"];
511 if (![client respondsToSelector:@selector(flushMailCaches) ])
513 return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
515 @"invalid client object (does not support flush)"];
518 [client flushMailCaches];
520 return [self redirectToLocation:@"view"];
525 // TODO: we might want to flush the caches?
529 if ((client = [self clientObject]) == nil) {
530 return [NSException exceptionWithHTTPStatus:404 /* Not Found */
531 reason:@"did not find mail folder"];
534 if ((error = [[self clientObject] expunge]) != nil)
537 if ([client respondsToSelector:@selector(flushMailCaches)])
538 [client flushMailCaches];
539 return [self redirectToLocation:@"view"];
542 - (id) emptyTrashAction
544 // TODO: we might want to flush the caches?
548 if ((client = [self clientObject]) == nil) {
549 error = [NSException exceptionWithHTTPStatus:404 /* Not Found */
550 reason:@"did not find mail folder"];
551 return [self redirectToViewWithError:error];
554 if (![client isKindOfClass:NSClassFromString(@"SOGoTrashFolder")]) {
555 /* would be better to move the method to an own class, but well .. */
556 error = [NSException exceptionWithHTTPStatus:400 /* Bad Request */
557 reason:@"method cannot be invoked on "
558 @"the specified object"];
559 return [self redirectToViewWithError:error];
562 /* mark all as deleted */
564 [self logWithFormat:@"TODO: must mark all as deleted for empty-trash"];
566 error = [[self clientObject] addFlagsToAllMessages:@"deleted"];
568 // TODO: improve error
569 return [self redirectToViewWithError:error];
573 if ((error = [[self clientObject] expunge]) != nil)
574 // TODO: improve error
575 return [self redirectToViewWithError:error];
577 if ([client respondsToSelector:@selector(flushMailCaches)])
578 [client flushMailCaches];
579 return [self redirectToLocation:@"view"];
582 /* folder operations */
584 - (id) createFolderAction
587 NSString *folderName;
590 folderName = [[[self context] request] formValueForKey:@"name"];
591 if ([folderName length] == 0) {
592 error = [NSException exceptionWithHTTPStatus:400 /* Bad Request */
593 reason:@"missing 'name' query parameter!"];
594 return [self redirectToViewWithError:error];
597 if ((client = [self clientObject]) == nil) {
598 error = [NSException exceptionWithHTTPStatus:404 /* Not Found */
599 reason:@"did not find mail folder"];
600 return [self redirectToViewWithError:error];
603 if ((error = [[self clientObject] davCreateCollection:folderName
604 inContext:[self context]]) != nil) {
605 return [self redirectToViewWithError:error];
608 return [self redirectToLocation:[folderName stringByAppendingString:@"/"]];
611 - (id) deleteFolderAction
617 if ((client = [self clientObject]) == nil) {
618 error = [NSException exceptionWithHTTPStatus:404 /* Not Found */
619 reason:@"did not find mail folder"];
620 return [self redirectToViewWithError:error];
623 /* jump to parent folder afterwards */
624 url = [[client container] baseURLInContext:[self context]];
625 if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
627 if ((error = [[self clientObject] delete]) != nil)
628 return [self redirectToViewWithError:error];
630 return [self redirectToLocation:url];
635 /* UIxMailListView */