]> err.no Git - scalable-opengroupware.org/blob - UI/MailPartViewers/UIxMailPartICalViewer.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1155 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / UI / MailPartViewers / UIxMailPartICalViewer.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   UIxMailPartICalViewer
24   
25   Show plain/calendar mail parts.
26 */
27
28 #import <NGExtensions/NSCalendarDate+misc.h>
29 #import <NGExtensions/NSNull+misc.h>
30 #import <NGExtensions/NSObject+Logs.h>
31
32 #import <NGImap4/NGImap4EnvelopeAddress.h>
33
34 #import <NGCards/iCalCalendar.h>
35 #import <NGCards/iCalEvent.h>
36 #import <NGCards/iCalPerson.h>
37 #import <NGCards/iCalDateTime.h>
38
39 #import <SoObjects/SOGo/SOGoDateFormatter.h>
40 #import <SoObjects/SOGo/SOGoUser.h>
41 #import <SoObjects/Appointments/SOGoAppointmentFolder.h>
42 #import <SoObjects/Appointments/SOGoAppointmentObject.h>
43 #import <SoObjects/Mailer/SOGoMailObject.h>
44
45 #import "UIxMailPartICalViewer.h"
46
47 @implementation UIxMailPartICalViewer
48
49 - (void)dealloc {
50   [self->storedEventObject release];
51   [self->storedEvent   release];
52   [self->attendee      release];
53   [self->item          release];
54   [self->inCalendar    release];
55   [self->inEvent       release];
56   [self->dateFormatter release];
57   [super dealloc];
58 }
59
60 /* maintain caches */
61
62 - (void)resetPathCaches {
63   [super resetPathCaches];
64   [self->inEvent           release]; self->inEvent           = nil;
65   [self->inCalendar        release]; self->inCalendar        = nil;
66   [self->storedEventObject release]; self->storedEventObject = nil;
67   [self->storedEvent       release]; self->storedEvent       = nil;
68   
69   /* not strictly path-related, but useless without it anyway: */
70   [self->attendee release]; self->attendee = nil;
71   [self->item     release]; self->item     = nil;
72 }
73
74 /* raw content handling */
75
76 - (NSStringEncoding)fallbackStringEncoding {
77   /*
78     iCalendar invitations sent by Outlook 2002 have the annoying bug that the
79     mail states an UTF-8 content encoding but the actual iCalendar content is
80     encoding in Latin-1 (or Windows Western?).
81     
82     As a result the content decoding will fail (TODO: always?). In this case we
83     try to decode with Latin-1.
84     
85     Note: we could check for the Outlook x-mailer, but it was considered better
86           to try Latin-1 as a fallback in any case (be tolerant).
87   */
88   return NSISOLatin1StringEncoding;
89 }
90
91 /* accessors */
92
93 - (iCalCalendar *) inCalendar
94 {
95   if (!inCalendar)
96     {
97       inCalendar
98         = [iCalCalendar parseSingleFromSource: [self flatContentAsString]];
99       [inCalendar retain];
100     }
101
102   return inCalendar;
103 }
104
105 - (BOOL)couldParseCalendar {
106   return [[self inCalendar] isNotNull];
107 }
108
109 - (iCalEvent *)inEvent {
110   NSArray *events;
111   
112   if (self->inEvent != nil)
113     return [self->inEvent isNotNull] ? self->inEvent : nil;
114   
115   events = [[self inCalendar] events];
116   if ([events count] > 0) {
117     self->inEvent = [[events objectAtIndex:0] retain];
118     return self->inEvent;
119   }
120   else {
121     self->inEvent = [[NSNull null] retain];
122     return nil;
123   }
124 }
125
126 /* formatters */
127
128 - (SOGoDateFormatter *)dateFormatter {
129   if (self->dateFormatter == nil) {
130     dateFormatter = [[context activeUser] dateFormatterInContext: context];
131     [dateFormatter retain];
132   }
133
134   return self->dateFormatter;
135 }
136
137 /* below is copied from UIxAppointmentView, can we avoid that? */
138
139 - (void)setAttendee:(id)_attendee {
140   ASSIGN(self->attendee, _attendee);
141 }
142 - (id)attendee {
143   return self->attendee;
144 }
145
146 - (NSString *) _personForDisplay: (iCalPerson *) person
147 {
148   return [NSString stringWithFormat: @"%@ <%@>",
149                    [person cnWithoutQuotes],
150                    [person rfc822Email]];
151 }
152
153 - (NSString *) attendeeForDisplay
154 {
155   return [self _personForDisplay: attendee];
156 }
157
158 - (void)setItem:(id)_item {
159   ASSIGN(self->item, _item);
160 }
161 - (id)item {
162   return self->item;
163 }
164
165 - (NSCalendarDate *) startTime
166 {
167   NSCalendarDate *date;
168   NSTimeZone *timeZone;
169   
170   date = [[self authorativeEvent] startDate];
171   timeZone = [[context activeUser] timeZone];
172   [date setTimeZone: timeZone];
173
174   return date;
175 }
176
177 - (NSCalendarDate *) endTime
178 {
179   NSCalendarDate *date;
180   NSTimeZone *timeZone;
181   
182   date = [[self authorativeEvent] endDate];
183   timeZone = [[context activeUser] timeZone];
184   [date setTimeZone: timeZone];
185
186   return date;
187 }
188
189 - (BOOL)isEndDateOnSameDay {
190   return [[self startTime] isDateOnSameDay:[self endTime]];
191 }
192 - (NSTimeInterval)duration {
193   return [[self endTime] timeIntervalSinceDate:[self startTime]];
194 }
195
196 /* calendar folder support */
197
198 - (id)calendarFolder {
199   /* return scheduling calendar of currently logged-in user */
200   SOGoUser *user;
201   id folder;
202
203   user = [context activeUser];
204   folder = [[user homeFolderInContext: context] lookupName: @"Calendar"
205                                                 inContext: context
206                                                 acquire: NO];
207
208   return folder;
209 }
210
211 - (id)storedEventObject {
212   /* lookup object in the users Calendar */
213   id calendar;
214   
215   if (self->storedEventObject != nil)
216     return [self->storedEventObject isNotNull] ? self->storedEventObject : nil;
217   
218   calendar = [self calendarFolder];
219   if ([calendar isKindOfClass:[NSException class]]) {
220     [self errorWithFormat:@"Did not find Calendar folder: %@", calendar];
221   }
222   else {
223     NSString *filename;
224     
225     filename = [calendar resourceNameForEventUID:[[self inEvent] uid]];
226     if (filename != nil) {
227       // TODO: When we get an exception, this might be an auth issue meaning
228       //       that the UID indeed exists but that the user has no access to
229       //       the object.
230       //       Of course this is quite unusual for the private calendar though.
231       id tmp;
232       
233       tmp = [calendar lookupName:filename inContext:[self context] acquire:NO];
234       if ([tmp isNotNull] && ![tmp isKindOfClass:[NSException class]])
235         self->storedEventObject = [tmp retain];
236     }
237   }
238   
239   if (self->storedEventObject == nil)
240     self->storedEventObject = [[NSNull null] retain];
241   
242   return self->storedEventObject;
243 }
244
245 - (BOOL)isEventStoredInCalendar {
246   return [[self storedEventObject] isNotNull];
247 }
248
249 - (iCalEvent *)storedEvent {
250   return (iCalEvent *) [(SOGoAppointmentObject *)[self storedEventObject] component: NO];
251 }
252
253 /* organizer tracking */
254
255 - (NSString *) loggedInUserEMail
256 {
257   NSDictionary *identity;
258
259   identity = [[context activeUser] primaryIdentity];
260
261   return [identity objectForKey: @"email"];
262 }
263
264 - (iCalEvent *)authorativeEvent {
265   /* DB is considered master, when in DB, ignore mail organizer */
266   return [self isEventStoredInCalendar]
267     ? [self storedEvent]
268     : [self inEvent];
269 }
270
271 - (BOOL)isLoggedInUserTheOrganizer {
272   NSString *loginEMail;
273   
274   if ((loginEMail = [self loggedInUserEMail]) == nil) {
275     [self warnWithFormat:@"Could not determine email of logged in user?"];
276     return NO;
277   }
278   
279   return [[self authorativeEvent] isOrganizer:loginEMail];
280 }
281
282 - (BOOL)isLoggedInUserAnAttendee {
283   NSString *loginEMail;
284   
285   if ((loginEMail = [self loggedInUserEMail]) == nil) {
286     [self warnWithFormat:@"Could not determine email of logged in user?"];
287     return NO;
288   }
289
290   return [[self authorativeEvent] isParticipant:loginEMail];
291 }
292
293 /* derived fields */
294
295 - (NSString *) organizerDisplayName
296 {
297   iCalPerson *organizer;
298   NSString *value;
299
300   organizer = [[self authorativeEvent] organizer];
301   if (organizer)
302     value = [self _personForDisplay: organizer];
303   else
304     value = @"[todo: no organizer set, use 'from']";
305
306   return value;
307 }
308
309 /* replies */
310
311 - (NGImap4EnvelopeAddress *)replySenderAddress {
312   /* 
313      The iMIP reply is the sender of the mail, the 'attendees' are NOT set to
314      the actual attendees. BUT the attendee field contains the reply-status!
315   */
316   id tmp;
317   
318   tmp = [[self clientObject] fromEnvelopeAddresses];
319   if ([tmp count] == 0) return nil;
320   return [tmp objectAtIndex:0];
321 }
322
323 - (NSString *)replySenderEMail {
324   return [[self replySenderAddress] email];
325 }
326 - (NSString *)replySenderBaseEMail {
327   return [[self replySenderAddress] baseEMail];
328 }
329
330 - (iCalPerson *)inReplyAttendee {
331   NSArray *attendees;
332   
333   attendees = [[self inEvent] attendees];
334   if ([attendees count] == 0)
335     return nil;
336   if ([attendees count] > 1)
337     [self warnWithFormat:@"More than one attendee in REPLY: %@", attendees];
338   
339   return [attendees objectAtIndex:0];
340 }
341 - (iCalPerson *)storedReplyAttendee {
342   /*
343     TODO: since an attendee can have multiple email addresses, maybe we
344           should translate the email to an internal uid and then retrieve
345           all emails addresses for matching the participant.
346           
347     Note: -findParticipantWithEmail: does not parse the email!
348   */
349   iCalEvent  *e;
350   iCalPerson *p;
351   
352   if ((e = [self storedEvent]) == nil)
353     return nil;
354   if ((p = [e findParticipantWithEmail:[self replySenderBaseEMail]]))
355     return p;
356   if ((p = [e findParticipantWithEmail:[self replySenderEMail]]))
357     return p;
358   return nil;
359 }
360 - (BOOL)isReplySenderAnAttendee {
361   return [[self storedReplyAttendee] isNotNull];
362 }
363
364 /* action URLs */
365
366 - (id)acceptLink {
367   return [[self pathToAttachmentObject] stringByAppendingString:@"/accept"];
368 }
369 - (id)declineLink {
370   return [[self pathToAttachmentObject] stringByAppendingString:@"/decline"];
371 }
372 - (id)tentativeLink {
373   return [[self pathToAttachmentObject] stringByAppendingString:@"/tentative"];
374 }
375
376 @end /* UIxMailPartICalViewer */