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