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