]> err.no Git - scalable-opengroupware.org/blob - UI/MailerUI/UIxMailListView.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1017 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / MailerUI / UIxMailListView.m
1 /*
2   Copyright (C) 2004-2005 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
22 /*
23   UIxMailListView
24   
25   This component represent a list of mails and is attached to an SOGoMailFolder
26   object.
27 */
28
29 #define messagesPerPage 50
30
31 #include "common.h"
32 #include <SoObjects/Mailer/SOGoMailFolder.h>
33 #include <SoObjects/Mailer/SOGoMailObject.h>
34 #include <NGObjWeb/SoObject+SoDAV.h>
35
36 #import "UIxMailListView.h"
37
38 static int attachmentFlagSize = 8096;
39
40 @implementation UIxMailListView
41
42 - (void) dealloc 
43 {
44   [self->qualifier  release];
45   [self->sortedUIDs release];
46   [self->messages   release];
47   [self->message    release];
48   [super dealloc];
49 }
50
51 /* notifications */
52
53 - (void) sleep 
54 {
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;
59   [super sleep];
60 }
61
62 /* accessors */
63
64 - (void)setMessage:(id)_msg
65 {
66   ASSIGN(self->message, _msg);
67 }
68
69 - (id) message 
70 {
71   return self->message;
72 }
73
74 - (void) setQualifier: (EOQualifier *) _msg 
75 {
76   ASSIGN(self->qualifier, _msg);
77 }
78
79 - (EOQualifier *) qualifier 
80 {
81   return self->qualifier;
82 }
83
84 - (BOOL) showToAddress 
85 {
86   NSString *ftype;
87   
88   ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
89   return [ftype isEqual:@"IPF.Sent"];
90 }
91
92 /* title */
93
94 - (NSString *) objectTitle 
95 {
96   return [[self clientObject] nameInContainer];
97 }
98
99 - (NSString *) panelTitle 
100 {
101   NSString *s;
102   
103   s = [self labelForKey:@"View Mail Folder"];
104   s = [s stringByAppendingString:@": "];
105   s = [s stringByAppendingString:[self objectTitle]];
106   return s;
107 }
108
109 /* derived accessors */
110
111 - (BOOL) isMessageDeleted 
112 {
113   NSArray *flags;
114   
115   flags = [[self message] valueForKey:@"flags"];
116   return [flags containsObject:@"deleted"];
117 }
118
119 - (BOOL) isMessageRead 
120 {
121   NSArray *flags;
122   
123   flags = [[self message] valueForKey:@"flags"];
124   return [flags containsObject:@"seen"];
125 }
126 - (NSString *) messageUidString 
127 {
128   return [[[self message] valueForKey:@"uid"] stringValue];
129 }
130
131 - (NSString *) messageCellStyleClass 
132 {
133   return [self isMessageDeleted]
134     ? @"mailer_listcell_deleted"
135     : @"mailer_listcell_regular";
136 }
137
138 - (NSString *) messageSubjectCellStyleClass 
139 {
140   return [NSString stringWithFormat: @"%@ %@",
141                    [self messageCellStyleClass],
142                    ([self isMessageRead]
143                     ? @"mailer_readmailsubject"
144                     : @"mailer_unreadmailsubject")];
145 }
146
147 - (BOOL) hasMessageAttachment 
148 {
149   /* we detect attachments by size ... */
150   unsigned size;
151   
152   size = [[[self message] valueForKey:@"size"] intValue];
153   return size > attachmentFlagSize;
154 }
155
156 /* fetching messages */
157
158 - (NSArray *) fetchKeys 
159 {
160   /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
161   static NSArray *keys = nil;
162   if (keys == nil) {
163     keys = [[NSArray alloc] initWithObjects:
164                               @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
165   }
166   return keys;
167 }
168
169 - (NSString *) defaultSortKey 
170 {
171   return @"DATE";
172 }
173
174 - (NSString *) imap4SortKey 
175 {
176   NSString *sort;
177   
178   sort = [[[self context] request] formValueForKey:@"sort"];
179
180   if ([sort length] == 0)
181     sort = [self defaultSortKey];
182
183   return [sort uppercaseString];
184 }
185
186 - (BOOL) isSortedDescending
187 {
188   NSString *desc;
189
190   desc = [[[self context] request] formValueForKey:@"desc"];
191
192   return ((desc)
193           ? [desc boolValue]
194           : YES);
195 }
196
197 - (NSString *) imap4SortOrdering 
198 {
199   NSString *sort;
200
201   sort = [self imap4SortKey];
202
203   if ([self isSortedDescending])
204     sort = [@"REVERSE " stringByAppendingString: sort];
205
206   return sort;
207 }
208
209 - (NSRange) fetchRange 
210 {
211   if (self->firstMessageNumber == 0)
212     return NSMakeRange(0, messagesPerPage);
213   return NSMakeRange(self->firstMessageNumber - 1, messagesPerPage);
214 }
215
216 - (NSArray *) sortedUIDs 
217 {
218   if (!sortedUIDs)
219     {
220       sortedUIDs 
221         = [[self clientObject] fetchUIDsMatchingQualifier: [self qualifier]
222                                sortOrdering: [self imap4SortOrdering]];
223       [sortedUIDs retain];
224     }
225
226   return self->sortedUIDs;
227 }
228
229 - (unsigned int) totalMessageCount 
230 {
231   return [self->sortedUIDs count];
232 }
233
234 - (BOOL) showsAllMessages 
235 {
236   return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
237 }
238
239 - (NSRange) fetchBlock 
240 {
241   NSRange  r;
242   unsigned len;
243   NSArray  *uids;
244   
245   r    = [self fetchRange];
246   uids = [self sortedUIDs];
247   
248   /* only need to restrict if we have a lot */
249   if ((len = [uids count]) <= r.length) {
250     r.location = 0;
251     r.length   = len;
252     return r;
253   }
254   
255   if (len < r.location) {
256     // TODO: CHECK CONDITION (< vs <=)
257     /* out of range, recover at first block */
258     r.location = 0;
259     return r;
260   }
261   
262   if (r.location + r.length > len)
263     r.length = len - r.location;
264   return r;
265 }
266
267 - (unsigned int) firstMessageNumber 
268 {
269   return [self fetchBlock].location + 1;
270 }
271
272 - (unsigned int) lastMessageNumber 
273 {
274   NSRange r;
275   
276   r = [self fetchBlock];
277   return r.location + r.length;
278 }
279
280 - (BOOL) hasPrevious 
281 {
282   return [self fetchBlock].location == 0 ? NO : YES;
283 }
284
285 - (BOOL) hasNext 
286 {
287   NSRange r = [self fetchBlock];
288   return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
289 }
290
291 - (unsigned int) nextFirstMessageNumber 
292 {
293   return [self firstMessageNumber] + [self fetchRange].length;
294 }
295
296 - (unsigned int) prevFirstMessageNumber 
297 {
298   NSRange  r;
299   unsigned idx;
300   
301   idx = [self firstMessageNumber];
302   r   = [self fetchRange];
303   if (idx > r.length)
304     return (idx - r.length);
305   return 1;
306 }
307
308 - (NSArray *) messages 
309 {
310   NSArray  *uids;
311   NSArray  *msgs;
312   NSRange  r;
313   unsigned len;
314   
315   if (self->messages != nil)
316     return self->messages;
317   
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];
323   
324   msgs = [[self clientObject] fetchUIDs:uids parts:[self fetchKeys]];
325   self->messages = [[msgs valueForKey:@"fetch"] retain];
326   return self->messages;
327 }
328
329 /* URL processing */
330
331 - (NSString *) messageViewTarget
332 {
333   return [NSString stringWithFormat: @"SOGo_msg_%@",
334                    [self messageUidString]];
335 }
336
337 - (NSString *) messageViewURL 
338 {
339   // TODO: noframe only when view-target is empty
340   // TODO: markread only if the message is unread
341   NSString *s;
342   
343   s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
344   if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
345   return s;
346 }
347 - (NSString *) markReadURL 
348 {
349   return [@"markMessageRead?uid=" stringByAppendingString:
350              [self messageUidString]];
351 }
352 - (NSString *) markUnreadURL 
353 {
354   return [@"markMessageUnread?uid=" stringByAppendingString:
355              [self messageUidString]];
356 }
357
358 /* JavaScript */
359
360 - (NSString *)msgRowID
361 {
362   return [@"row_" stringByAppendingString:[self messageUidString]];
363 }
364
365 - (NSString *)msgDivID
366 {
367   return [@"div_" stringByAppendingString:[self messageUidString]];
368 }
369
370 - (NSString *)msgIconReadImgID
371 {
372   return [@"readdiv_" stringByAppendingString:[self messageUidString]];
373 }
374
375 - (NSString *)msgIconUnreadImgID
376 {
377   return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
378 }
379
380 /* error redirects */
381
382 - (id) redirectToViewWithError: (id) _error 
383 {
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)
388   id url;
389   
390   if (![_error isNotNull])
391     return [self redirectToLocation:@"view"];
392   
393   if ([_error isKindOfClass:[NSException class]])
394     _error = [_error reason];
395   else if ([_error isKindOfClass:[NSString class]])
396     _error = [_error stringValue];
397   
398   url = [_error stringByEscapingURL];
399   url = [@"view?error=" stringByAppendingString:url];
400   return [self redirectToLocation:url];
401 }
402
403 /* active message */
404
405 - (SOGoMailObject *) lookupActiveMessage 
406 {
407   NSString *uid;
408   
409   if ((uid = [[[self context] request] formValueForKey:@"uid"]) == nil)
410     return nil;
411
412   return [[self clientObject] lookupName:uid inContext:[self context]
413                               acquire:NO];
414 }
415
416 /* actions */
417
418 - (BOOL) isJavaScriptRequest 
419 {
420   return [[[[self context] request] formValueForKey:@"jsonly"] boolValue];
421 }
422
423 - (id) javaScriptOK 
424 {
425   WOResponse *r;
426
427   r = [[self context] response];
428   [r setStatus:200 /* OK */];
429   return r;
430 }
431
432 - (int) firstMessageOfPageFor: (int) messageNbr
433 {
434   NSArray *messageNbrs;
435   int nbrInArray;
436   int firstMessage;
437
438   messageNbrs = [self sortedUIDs];
439   nbrInArray
440     = [messageNbrs indexOfObject: [NSNumber numberWithInt: messageNbr]];
441   if (nbrInArray > -1)
442     firstMessage = ((int) (nbrInArray / messagesPerPage)
443                     * messagesPerPage) + 1;
444   else
445     firstMessage = 1;
446
447   return firstMessage;
448 }
449
450 - (id) defaultAction 
451 {
452   WORequest *request;
453   NSString *specificMessage;
454
455   request = [[self context] request];
456
457   [[self clientObject] flushMailCaches];
458
459   specificMessage = [request formValueForKey: @"pageforuid"];
460   self->firstMessageNumber
461     = ((specificMessage)
462        ? [self firstMessageOfPageFor: [specificMessage intValue]]
463        : [[request formValueForKey:@"idx"] intValue]);
464
465   return self;
466 }
467
468 - (id) viewAction 
469 {
470   return [self defaultAction];
471 }
472
473 - (id) markMessageUnreadAction 
474 {
475   NSException *error;
476   
477   if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
478     // TODO: improve error handling
479     return error;
480
481   if ([self isJavaScriptRequest])
482     return [self javaScriptOK];
483   
484   return [self redirectToLocation:@"view"];
485 }
486
487 - (id) markMessageReadAction 
488 {
489   NSException *error;
490   
491   if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
492     // TODO: improve error handling
493     return error;
494   
495   if ([self isJavaScriptRequest])
496     return [self javaScriptOK];
497   
498   return [self redirectToLocation:@"view"];
499 }
500
501 - (id) getMailAction 
502 {
503   // TODO: we might want to flush the caches?
504   id client;
505
506   if ((client = [self clientObject]) == nil) {
507     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
508                         reason:@"did not find mail folder"];
509   }
510
511   if (![client respondsToSelector:@selector(flushMailCaches) ]) 
512     {
513       return [NSException exceptionWithHTTPStatus: 500 /* Server Error */
514                           reason:
515                             @"invalid client object (does not support flush)"];
516     }
517
518   [client flushMailCaches];
519
520   return [self redirectToLocation:@"view"];
521 }
522
523 - (id) expungeAction 
524 {
525   // TODO: we might want to flush the caches?
526   NSException *error;
527   id client;
528   
529   if ((client = [self clientObject]) == nil) {
530     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
531                         reason:@"did not find mail folder"];
532   }
533   
534   if ((error = [[self clientObject] expunge]) != nil)
535     return error;
536   
537   if ([client respondsToSelector:@selector(flushMailCaches)])
538     [client flushMailCaches];
539   return [self redirectToLocation:@"view"];
540 }
541
542 - (id) emptyTrashAction 
543 {
544   // TODO: we might want to flush the caches?
545   NSException *error;
546   id client;
547   
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];
552   }
553
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];
560   }
561   
562   /* mark all as deleted */
563
564   [self logWithFormat:@"TODO: must mark all as deleted for empty-trash"];
565   
566   error = [[self clientObject] addFlagsToAllMessages:@"deleted"];
567   if (error != nil)
568     // TODO: improve error
569     return [self redirectToViewWithError:error];
570   
571   /* expunge */
572   
573   if ((error = [[self clientObject] expunge]) != nil)
574     // TODO: improve error
575     return [self redirectToViewWithError:error];
576   
577   if ([client respondsToSelector:@selector(flushMailCaches)])
578     [client flushMailCaches];
579   return [self redirectToLocation:@"view"];
580 }
581
582 /* folder operations */
583
584 - (id) createFolderAction 
585 {
586   NSException *error;
587   NSString    *folderName;
588   id client;
589   
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];
595   }
596   
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];
601   }
602   
603   if ((error = [[self clientObject] davCreateCollection:folderName
604                                     inContext:[self context]]) != nil) {
605     return [self redirectToViewWithError:error];
606   }
607   
608   return [self redirectToLocation:[folderName stringByAppendingString:@"/"]];
609 }
610
611 - (id) deleteFolderAction 
612 {
613   NSException *error;
614   NSString *url;
615   id client;
616   
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];
621   }
622   
623   /* jump to parent folder afterwards */
624   url = [[client container] baseURLInContext:[self context]];
625   if (![url hasSuffix:@"/"]) url = [url stringByAppendingString:@"/"];
626   
627   if ((error = [[self clientObject] delete]) != nil)
628     return [self redirectToViewWithError:error];
629   
630   return [self redirectToLocation:url];
631 }
632
633 @end
634
635 /* UIxMailListView */