]> err.no Git - scalable-opengroupware.org/blob - UI/MailPartViewers/UIxMailPartICalViewer.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1236 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 {
200   /* return scheduling calendar of currently logged-in user */
201   SOGoUser *user;
202   id folder;
203
204   user = [context activeUser];
205   folder = [[user homeFolderInContext: context] lookupName: @"Calendar"
206                                                 inContext: context
207                                                 acquire: NO];
208
209   return [folder lookupName: @"personal" inContext: context acquire: NO];
210 }
211
212 - (id)storedEventObject {
213   /* lookup object in the users Calendar */
214   id calendar;
215   
216   if (self->storedEventObject != nil)
217     return [self->storedEventObject isNotNull] ? self->storedEventObject : nil;
218   
219   calendar = [self calendarFolder];
220   if ([calendar isKindOfClass:[NSException class]]) {
221     [self errorWithFormat:@"Did not find Calendar folder: %@", calendar];
222   }
223   else {
224     NSString *filename;
225     
226     filename = [calendar resourceNameForEventUID:[[self inEvent] uid]];
227     if (filename != nil) {
228       // TODO: When we get an exception, this might be an auth issue meaning
229       //       that the UID indeed exists but that the user has no access to
230       //       the object.
231       //       Of course this is quite unusual for the private calendar though.
232       id tmp;
233       
234       tmp = [calendar lookupName:filename inContext:[self context] acquire:NO];
235       if ([tmp isNotNull] && ![tmp isKindOfClass:[NSException class]])
236         self->storedEventObject = [tmp retain];
237     }
238   }
239   
240   if (self->storedEventObject == nil)
241     self->storedEventObject = [[NSNull null] retain];
242   
243   return self->storedEventObject;
244 }
245
246 - (BOOL)isEventStoredInCalendar {
247   return [[self storedEventObject] isNotNull];
248 }
249
250 - (iCalEvent *)storedEvent {
251   return (iCalEvent *) [(SOGoAppointmentObject *)[self storedEventObject] component: NO];
252 }
253
254 /* organizer tracking */
255
256 - (NSString *) loggedInUserEMail
257 {
258   NSDictionary *identity;
259
260   identity = [[context activeUser] primaryIdentity];
261
262   return [identity objectForKey: @"email"];
263 }
264
265 - (iCalEvent *)authorativeEvent {
266   /* DB is considered master, when in DB, ignore mail organizer */
267   return [self isEventStoredInCalendar]
268     ? [self storedEvent]
269     : [self inEvent];
270 }
271
272 - (BOOL)isLoggedInUserTheOrganizer {
273   NSString *loginEMail;
274   
275   if ((loginEMail = [self loggedInUserEMail]) == nil) {
276     [self warnWithFormat:@"Could not determine email of logged in user?"];
277     return NO;
278   }
279   
280   return [[self authorativeEvent] isOrganizer:loginEMail];
281 }
282
283 - (BOOL)isLoggedInUserAnAttendee {
284   NSString *loginEMail;
285   
286   if ((loginEMail = [self loggedInUserEMail]) == nil) {
287     [self warnWithFormat:@"Could not determine email of logged in user?"];
288     return NO;
289   }
290
291   return [[self authorativeEvent] isParticipant:loginEMail];
292 }
293
294 /* derived fields */
295
296 - (NSString *) organizerDisplayName
297 {
298   iCalPerson *organizer;
299   NSString *value;
300
301   organizer = [[self authorativeEvent] organizer];
302   if (organizer)
303     value = [self _personForDisplay: organizer];
304   else
305     value = @"[todo: no organizer set, use 'from']";
306
307   return value;
308 }
309
310 /* replies */
311
312 - (NGImap4EnvelopeAddress *)replySenderAddress {
313   /* 
314      The iMIP reply is the sender of the mail, the 'attendees' are NOT set to
315      the actual attendees. BUT the attendee field contains the reply-status!
316   */
317   id tmp;
318   
319   tmp = [[self clientObject] fromEnvelopeAddresses];
320   if ([tmp count] == 0) return nil;
321   return [tmp objectAtIndex:0];
322 }
323
324 - (NSString *)replySenderEMail {
325   return [[self replySenderAddress] email];
326 }
327 - (NSString *)replySenderBaseEMail {
328   return [[self replySenderAddress] baseEMail];
329 }
330
331 - (iCalPerson *)inReplyAttendee {
332   NSArray *attendees;
333   
334   attendees = [[self inEvent] attendees];
335   if ([attendees count] == 0)
336     return nil;
337   if ([attendees count] > 1)
338     [self warnWithFormat:@"More than one attendee in REPLY: %@", attendees];
339   
340   return [attendees objectAtIndex:0];
341 }
342 - (iCalPerson *)storedReplyAttendee {
343   /*
344     TODO: since an attendee can have multiple email addresses, maybe we
345           should translate the email to an internal uid and then retrieve
346           all emails addresses for matching the participant.
347           
348     Note: -findParticipantWithEmail: does not parse the email!
349   */
350   iCalEvent  *e;
351   iCalPerson *p;
352   
353   if ((e = [self storedEvent]) == nil)
354     return nil;
355   if ((p = [e findParticipantWithEmail:[self replySenderBaseEMail]]))
356     return p;
357   if ((p = [e findParticipantWithEmail:[self replySenderEMail]]))
358     return p;
359   return nil;
360 }
361 - (BOOL)isReplySenderAnAttendee {
362   return [[self storedReplyAttendee] isNotNull];
363 }
364
365 /* action URLs */
366
367 - (id)acceptLink {
368   return [[self pathToAttachmentObject] stringByAppendingString:@"/accept"];
369 }
370 - (id)declineLink {
371   return [[self pathToAttachmentObject] stringByAppendingString:@"/decline"];
372 }
373 - (id)tentativeLink {
374   return [[self pathToAttachmentObject] stringByAppendingString:@"/tentative"];
375 }
376
377 @end /* UIxMailPartICalViewer */