]> err.no Git - scalable-opengroupware.org/blob - SOGo/UI/Mailer/UIxMailListView.m
implemented async read/unread marking
[scalable-opengroupware.org] / SOGo / UI / Mailer / 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
54 @implementation UIxMailListView
55
56 static int attachmentFlagSize = 8096;
57
58 - (void)dealloc {
59   [self->qualifier  release];
60   [self->sortedUIDs release];
61   [self->messages   release];
62   [self->message    release];
63   [super dealloc];
64 }
65
66 /* notifications */
67
68 - (void)sleep {
69   [self->qualifier  release]; self->qualifier  = nil;
70   [self->sortedUIDs release]; self->sortedUIDs = nil;
71   [self->messages   release]; self->messages   = nil;
72   [self->message    release]; self->message    = nil;
73   [super sleep];
74 }
75
76 /* accessors */
77
78 - (void)setMessage:(id)_msg {
79   ASSIGN(self->message, _msg);
80 }
81 - (id)message {
82   return self->message;
83 }
84
85 - (void)setQualifier:(EOQualifier *)_msg {
86   ASSIGN(self->qualifier, _msg);
87 }
88 - (EOQualifier *)qualifier {
89   return self->qualifier;
90 }
91
92 - (BOOL)showToAddress {
93   NSString *ftype;
94   
95   ftype = [[self clientObject] valueForKey:@"outlookFolderClass"];
96   return [ftype isEqual:@"IPF.Sent"];
97 }
98
99 /* title */
100
101 - (NSString *)objectTitle {
102   return [[self clientObject] nameInContainer];
103 }
104 - (NSString *)panelTitle {
105   NSString *s;
106   
107   s = [self labelForKey:@"View Mail Folder"];
108   s = [s stringByAppendingString:@": "];
109   s = [s stringByAppendingString:[self objectTitle]];
110   return s;
111 }
112
113 /* derived accessors */
114
115 - (BOOL)isMessageDeleted {
116   NSArray *flags;
117   
118   flags = [[self message] valueForKey:@"flags"];
119   return [flags containsObject:@"deleted"];
120 }
121
122 - (BOOL)isMessageRead {
123   NSArray *flags;
124   
125   flags = [[self message] valueForKey:@"flags"];
126   return [flags containsObject:@"seen"];
127 }
128 - (NSString *)messageUidString {
129   return [[[self message] valueForKey:@"uid"] stringValue];
130 }
131
132 - (NSString *)messageSubjectStyleClass {
133   return [self isMessageRead]
134     ? @"mailer_readmailsubject"
135     : @"mailer_unreadmailsubject";
136 }
137 - (NSString *)messageCellStyleClass {
138   return [self isMessageDeleted]
139     ? @"mailer_listcell_deleted"
140     : @"mailer_listcell_regular";
141 }
142
143 - (BOOL)hasMessageAttachment {
144   /* we detect attachments by size ... */
145   unsigned size;
146   
147   size = [[[self message] valueForKey:@"size"] intValue];
148   return size > attachmentFlagSize;
149 }
150
151 /* fetching messages */
152
153 - (NSArray *)fetchKeys {
154   /* Note: see SOGoMailManager.m for allowed IMAP4 keys */
155   static NSArray *keys = nil;
156   if (keys == nil) {
157     keys = [[NSArray alloc] initWithObjects:
158                               @"FLAGS", @"ENVELOPE", @"RFC822.SIZE", nil];
159   }
160   return keys;
161 }
162
163 - (NSString *)defaultSortKey {
164   return @"DATE";
165 }
166 - (NSString *)imap4SortKey {
167   NSString *sort;
168   
169   sort = [[[self context] request] formValueForKey:@"sort"];
170   
171   if ([sort length] == 0)
172     sort = [self defaultSortKey];
173   return [sort uppercaseString];
174 }
175
176 - (BOOL)isSortedDescending {
177   NSString *desc;
178   
179   desc = [[[self context] request] formValueForKey:@"desc"];
180   if(!desc)
181     return NO;
182   return [desc boolValue] ? YES : NO;
183 }
184
185 - (NSString *)imap4SortOrdering {
186   NSString *sort;
187   
188   sort = [self imap4SortKey];
189   if(![self isSortedDescending])
190     return sort;
191   return [@"REVERSE " stringByAppendingString:sort];
192 }
193
194 - (NSRange)fetchRange {
195   if (self->firstMessageNumber == 0)
196     return NSMakeRange(0, 50);
197   return NSMakeRange(self->firstMessageNumber - 1, 50);
198 }
199
200 - (NSArray *)sortedUIDs {
201   if (self->sortedUIDs != nil)
202     return self->sortedUIDs;
203   
204   self->sortedUIDs 
205     = [[[self clientObject] fetchUIDsMatchingQualifier:[self qualifier]
206                             sortOrdering:[self imap4SortOrdering]] retain];
207   return self->sortedUIDs;
208 }
209 - (unsigned int)totalMessageCount {
210   return [self->sortedUIDs count];
211 }
212 - (BOOL)showsAllMessages {
213   return ([[self sortedUIDs] count] <= [self fetchRange].length) ? YES : NO;
214 }
215
216 - (NSRange)fetchBlock {
217   NSRange  r;
218   unsigned len;
219   NSArray  *uids;
220   
221   r    = [self fetchRange];
222   uids = [self sortedUIDs];
223   
224   /* only need to restrict if we have a lot */
225   if ((len = [uids count]) <= r.length) {
226     r.location = 0;
227     r.length   = len;
228     return r;
229   }
230   
231   if (len < r.location) {
232     // TODO: CHECK CONDITION (< vs <=)
233     /* out of range, recover at first block */
234     r.location = 0;
235     return r;
236   }
237   
238   if (r.location + r.length > len)
239     r.length = len - r.location;
240   return r;
241 }
242 - (unsigned int)firstMessageNumber {
243   return [self fetchBlock].location + 1;
244 }
245 - (unsigned int)lastMessageNumber {
246   NSRange r;
247   
248   r = [self fetchBlock];
249   return r.location + r.length;
250 }
251 - (BOOL)hasPrevious {
252   return [self fetchBlock].location == 0 ? NO : YES;
253 }
254 - (BOOL)hasNext {
255   NSRange r = [self fetchBlock];
256   return r.location + r.length >= [[self sortedUIDs] count] ? NO : YES;
257 }
258
259 - (unsigned int)nextFirstMessageNumber {
260   return [self firstMessageNumber] + [self fetchRange].length;
261 }
262 - (unsigned int)prevFirstMessageNumber {
263   NSRange  r;
264   unsigned idx;
265   
266   idx = [self firstMessageNumber];
267   r   = [self fetchRange];
268   if (idx > r.length)
269     return (idx - r.length);
270   return 1;
271 }
272
273 - (NSArray *)messages {
274   NSArray  *uids;
275   NSArray  *msgs;
276   NSRange  r;
277   unsigned len;
278   
279   if (self->messages != nil)
280     return self->messages;
281   
282   r    = [self fetchBlock];
283   uids = [self sortedUIDs];
284   if ((len = [uids count]) > r.length)
285     /* only need to restrict if we have a lot */
286     uids = [uids subarrayWithRange:r];
287   
288   msgs = [[self clientObject] fetchUIDs:uids parts:[self fetchKeys]];
289   self->messages = [[msgs valueForKey:@"fetch"] retain];
290   return self->messages;
291 }
292
293 /* URL processing */
294
295 - (NSString *)messageViewTarget {
296   return [@"SOGo_msg_" stringByAppendingString:[self messageUidString]];
297 }
298 - (NSString *)messageViewURL {
299   // TODO: noframe only when view-target is empty
300   // TODO: markread only if the message is unread
301   NSString *s;
302   
303   s = [[self messageUidString] stringByAppendingString:@"/view?noframe=1"];
304   if (![self isMessageRead]) s = [s stringByAppendingString:@"&markread=1"];
305   return s;
306 }
307 - (NSString *)markReadURL {
308   return [@"markMessageRead?uid=" stringByAppendingString:
309              [self messageUidString]];
310 }
311 - (NSString *)markUnreadURL {
312   return [@"markMessageUnread?uid=" stringByAppendingString:
313              [self messageUidString]];
314 }
315
316 /* JavaScript */
317
318 - (NSString *)msgRowID {
319   return [@"row_" stringByAppendingString:[self messageUidString]];
320 }
321 - (NSString *)msgDivID {
322   return [@"div_" stringByAppendingString:[self messageUidString]];
323 }
324
325 - (NSString *)msgIconReadDivID {
326   return [@"readdiv_" stringByAppendingString:[self messageUidString]];
327 }
328 - (NSString *)msgIconUnreadDivID {
329   return [@"unreaddiv_" stringByAppendingString:[self messageUidString]];
330 }
331 - (NSString *)msgIconReadVisibility {
332   return [self isMessageRead] ? nil : @"display: none;";
333 }
334 - (NSString *)msgIconUnreadVisibility {
335   return [self isMessageRead] ? @"display: none;" : nil;
336 }
337
338 - (NSString *)clickedMsgJS {
339   /* return 'false' aborts processing */
340   return [NSString stringWithFormat:@"clickedUid(this, '%@'); return false", 
341                      [self messageUidString]];
342 }
343
344 // the following are unused?
345 - (NSString *)dblClickedMsgJS {
346   return [NSString stringWithFormat:@"doubleClickedUid(this, '%@')", 
347                      [self messageUidString]];
348 }
349
350 // the following are unused?
351 - (NSString *)highlightRowJS {
352   return [NSString stringWithFormat:@"highlightUid(this, '%@')", 
353                      [self messageUidString]];
354 }
355 - (NSString *)lowlightRowJS {
356   return [NSString stringWithFormat:@"lowlightUid(this, '%@')", 
357                      [self messageUidString]];
358 }
359
360 - (NSString *)markUnreadJS {
361   return [NSString stringWithFormat:
362                      @"mailListMarkMessage(this, 'markMessageUnread', "
363                      @"'%@', false)", 
364                      [self messageUidString]];
365 }
366 - (NSString *)markReadJS {
367   return [NSString stringWithFormat:
368                      @"mailListMarkMessage(this, 'markMessageRead', "
369                      @"'%@', true)", 
370                      [self messageUidString]];
371 }
372
373 - (NSString *)jsCode {
374   static NSString *script = \
375   @"var rowSelectionCount = 0;\n"
376   @"\n"
377   @"validateControls();\n"
378   @"\n"
379   @"function showElement(e, shouldShow) {\n"
380   @"    e.style.display = shouldShow ? \"\" : \"none\";\n"
381   @"}\n"
382   @"\n"
383   @"function enableElement(e, shouldEnable) {\n"
384   @"  if(!e)\n"
385   @"    return;\n"
386   @"  if(shouldEnable) {\n"
387   @"    if(e.hasAttribute(\"disabled\"))\n"
388   @"      e.removeAttribute(\"disabled\");\n"
389   @"  }\n"
390   @"  else {\n"
391   @"    e.setAttribute(\"disabled\", \"1\");\n"
392   @"  }\n"
393   @"}\n"
394   @"\n"
395   @"function toggleRowSelectionStatus(sender) {\n"
396   @"  rowID = sender.value;\n"
397   @"  tr = document.getElementById(rowID);\n"
398   @"  if(sender.checked) {\n"
399   @"    tr.className = \"tableview_selected\";\n"
400   @"    rowSelectionCount += 1;\n"
401   @"  }\n"
402   @"  else {\n"
403   @"    tr.className = \"tableview\";\n"
404   @"    rowSelectionCount -= 1;\n"
405   @"  }\n"
406   @"  this.validateControls();\n"
407   @"}\n"
408   @"\n"
409   @"function validateControls() {\n"
410   @"  var e = document.getElementById(\"moveto\");\n"
411   @"  this.enableElement(e, rowSelectionCount > 0);\n"
412   @"}\n"
413   @"\n"
414   @"function moveTo(uri) {\n"
415   @"  alert(\"MoveTo: \" + uri);\n"
416   @"}\n"
417   @"";
418   return script;
419 }
420
421 /* active message */
422
423 - (SOGoMailObject *)lookupActiveMessage {
424   NSString *uid;
425   
426   if ((uid = [[[self context] request] formValueForKey:@"uid"]) == nil)
427     return nil;
428
429   return [[self clientObject] lookupName:uid inContext:[self context]
430                               acquire:NO];
431 }
432
433 /* actions */
434
435 - (id)defaultAction {
436   self->firstMessageNumber = 
437     [[[[self context] request] formValueForKey:@"idx"] intValue];
438   return self;
439 }
440
441 - (BOOL)isJavaScriptRequest {
442   return [[[[self context] request] formValueForKey:@"jsonly"] boolValue];
443 }
444 - (id)javaScriptOK {
445   WOResponse *r;
446
447   r = [[self context] response];
448   [r setStatus:200 /* OK */];
449   return r;
450 }
451
452 - (id)markMessageUnreadAction {
453   NSException *error;
454   
455   if ((error = [[self lookupActiveMessage] removeFlags:@"seen"]) != nil)
456     // TODO: improve error handling
457     return error;
458
459   if ([self isJavaScriptRequest])
460     return [self javaScriptOK];
461   
462   return [self redirectToLocation:@"view"];
463 }
464 - (id)markMessageReadAction {
465   NSException *error;
466   
467   if ((error = [[self lookupActiveMessage] addFlags:@"seen"]) != nil)
468     // TODO: improve error handling
469     return error;
470   
471   if ([self isJavaScriptRequest])
472     return [self javaScriptOK];
473   
474   return [self redirectToLocation:@"view"];
475 }
476
477 - (id)getMailAction {
478   // TODO: we might want to flush the caches?
479   id client;
480   
481   if ((client = [self clientObject]) == nil) {
482     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
483                         reason:@"did not find mail folder"];
484   }
485   
486   if (![client respondsToSelector:@selector(flushMailCaches)]) {
487     return [NSException exceptionWithHTTPStatus:500 /* Server Error */
488                         reason:
489                           @"invalid client object (does not support flush)"];
490   }
491   
492   [client flushMailCaches];
493   return [self redirectToLocation:@"view"];
494 }
495
496 - (id)expungeAction {
497   // TODO: we might want to flush the caches?
498   NSException *error;
499   id client;
500   
501   if ((client = [self clientObject]) == nil) {
502     return [NSException exceptionWithHTTPStatus:404 /* Not Found */
503                         reason:@"did not find mail folder"];
504   }
505   
506   if ((error = [[self clientObject] expunge]) != nil)
507     return error;
508   
509   if ([client respondsToSelector:@selector(flushMailCaches)])
510     [client flushMailCaches];
511   return [self redirectToLocation:@"view"];
512 }
513
514 @end /* UIxMailListView */