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