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