+2005-07-06 Marcus Mueller <znek@mulle-kybernetik.com>
+
+ * v0.9.35
+
+ * GNUMakefile: added SOGoFreeBusyObject.m
+
+ * product.plist: added SOGoFreeBusyObject
+
+ * SOGoFreeBusyObject.[hm]: new class resembling free/busy information
+ for a particular user. Because free/busy information isn't solely
+ based upon appointments, this is now a part of the user folder.
+ However the information contained therein is still based on
+ information provided by appointments, only. This should be fixed in
+ a later release.
+
+ * SOGoAppointmentFolder.m: new API to lookup freeBusyObjects for an
+ array of uids.
+
2005-07-05 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointmentFolder.m: fetch new priority field in core infos
SOGoAppointmentObject.m \
SOGoAppointmentFolder.m \
SOGoGroupAppointmentFolder.m \
+ SOGoFreeBusyObject.m \
Appointments_RESOURCE_FILES += \
Version \
- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx;
- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx;
+- (NSArray *)lookupFreeBusyObjectsForUIDs:(NSArray *)_uids inContext:(id)_ctx;
- (NSArray *)uidsFromICalPersons:(NSArray *)_persons;
- (NSArray *)lookupCalendarFoldersForICalPerson:(NSArray *)_persons
{
static NSArray *infos = nil; // TODO: move to a plist file
if (infos == nil) {
- infos = [[NSArray alloc] init];
+ infos = [[NSArray alloc] initWithObjects:@"partmails", @"partstates", nil];
}
return [self fetchFields:infos from:_startDate to:_endDate];
}
return folders;
}
+- (NSArray *)lookupFreeBusyObjectsForUIDs:(NSArray *)_uids inContext:(id)_ctx {
+ /* Note: can return NSNull objects in the array! */
+ NSMutableArray *objs;
+ NSEnumerator *e;
+ NSString *uid;
+
+ if ([_uids count] == 0) return nil;
+ objs = [NSMutableArray arrayWithCapacity:16];
+ e = [_uids objectEnumerator];
+ while ((uid = [e nextObject])) {
+ id obj;
+
+ obj = [self lookupHomeFolderForUID:uid inContext:nil];
+ if ([obj isNotNull]) {
+ obj = [obj lookupName:@"freebusy.ifb" inContext:nil acquire:NO];
+ if ([obj isKindOfClass:[NSException class]])
+ obj = nil;
+ }
+ if (![obj isNotNull])
+ [self logWithFormat:@"Note: did not find freebusy.ifb for uid: '%@'", uid];
+
+ /* Note: intentionally add 'null' folders to allow a mapping */
+ [objs addObject:obj ? obj : [NSNull null]];
+ }
+ return objs;
+}
+
- (NSArray *)uidsFromICalPersons:(NSArray *)_persons {
/* Note: can return NSNull objects in the array! */
NSMutableArray *uids;
--- /dev/null
+/*
+ Copyright (C) 2000-2004 SKYRIX Software AG
+
+ This file is part of OGo
+
+ OGo is free software; you can redistribute it and/or modify it under
+ the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with OGo; see the file COPYING. If not, write to the
+ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+ 02111-1307, USA.
+*/
+// $Id$
+
+
+#ifndef __Appointments_SOGoFreeBusyObject_H_
+#define __Appointments_SOGoFreeBusyObject_H_
+
+#include <SOGo/SOGoContentObject.h>
+
+/*
+ SOGoFreeBusyObject
+
+ Represents Free/Busy information for a single user as specified in RFC2445.
+*/
+
+@class NSArray, NSCalendarDate;
+
+@interface SOGoFreeBusyObject : SOGoContentObject
+{
+}
+
+/* accessors */
+
+- (NSString *)iCalString;
+
+- (NSString *)contentAsStringFrom:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate;
+
+- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate;
+
+@end
+
+#endif /* __Appointments_SOGoFreeBusyObject_H_ */
--- /dev/null
+/*
+ Copyright (C) 2000-2004 SKYRIX Software AG
+
+ This file is part of OGo
+
+ OGo is free software; you can redistribute it and/or modify it under
+ the terms of the GNU Lesser General Public License as published by the
+ Free Software Foundation; either version 2, or (at your option) any
+ later version.
+
+ OGo is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with OGo; see the file COPYING. If not, write to the
+ Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
+ 02111-1307, USA.
+*/
+// $Id$
+
+#include "SOGoFreeBusyObject.h"
+#include "common.h"
+#include <SOGo/AgenorUserManager.h>
+#include <NGiCal/NGiCal.h>
+#include <NGiCal/iCalRenderer.h>
+
+@interface NSDate(UsedPrivates)
+- (NSString *)icalString; // declared in NGiCal
+@end
+
+@interface SOGoFreeBusyObject (PrivateAPI)
+- (NSString *)iCalStringForFreeBusyInfos:(NSArray *)_infos
+ from:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate;
+@end
+
+@implementation SOGoFreeBusyObject
+
+- (NSString *)iCalString {
+ // for UI-X appointment viewer
+ return [self contentAsString];
+}
+
+- (NSString *)contentAsString {
+ NSCalendarDate *startDate, *endDate;
+
+ startDate = [[[NSCalendarDate calendarDate] mondayOfWeek] beginOfDay];
+ endDate = [startDate dateByAddingYears:0
+ months:0
+ days:7
+ hours:23
+ minutes:59
+ seconds:59];
+ return [self contentAsStringFrom:startDate to:endDate];
+}
+
+- (NSString *)contentAsStringFrom:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate
+{
+ NSArray *infos;
+
+ infos = [self fetchFreebusyInfosFrom:_startDate to:_endDate];
+ return [self iCalStringForFreeBusyInfos:infos from:_startDate to:_endDate];
+}
+
+- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate
+{
+ id userFolder, calFolder;
+ NSArray *infos;
+ AgenorUserManager *um;
+ NSString *email;
+ NSMutableArray *filtered;
+ unsigned i, count;
+
+ userFolder = [self container];
+ calFolder = [userFolder lookupName:@"Calendar" inContext:nil acquire:NO];
+ infos = [calFolder fetchFreebusyInfosFrom:_startDate to:_endDate];
+ um = [AgenorUserManager sharedUserManager];
+ email = [um getEmailForUID:[userFolder login]];
+ count = [infos count];
+ filtered = [[[NSMutableArray alloc] initWithCapacity:count] autorelease];
+
+ for (i = 0; i < count; i++) {
+ NSDictionary *info;
+ NSArray *partmails;
+ unsigned p, pCount;
+
+ info = [infos objectAtIndex:i];
+ partmails = [[info objectForKey:@"partmails"]
+ componentsSeparatedByString:@"\n"];
+ pCount = [partmails count];
+ for (p = 0; p < pCount; p++) {
+ NSString *pEmail;
+
+ pEmail = [partmails objectAtIndex:p];
+ if ([pEmail isEqualToString:email]) {
+ NSArray *partstates;
+ NSString *state;
+
+ partstates = [[info objectForKey:@"partstates"]
+ componentsSeparatedByString:@"\n"];
+ state = [partstates objectAtIndex:p];
+ if ([state intValue] == iCalPersonPartStatAccepted) {
+ // TODO: add tentative apts as well, but put state and email
+ // into info
+ [filtered addObject:info];
+ }
+ break;
+ }
+ }
+ }
+ return filtered;
+}
+
+/* Private API */
+
+- (NSString *)iCalStringForFreeBusyInfos:(NSArray *)_infos
+ from:(NSCalendarDate *)_startDate
+ to:(NSCalendarDate *)_endDate
+{
+ AgenorUserManager *um;
+ NSMutableString *ms;
+ NSString *uid, *x;
+ unsigned i, count;
+
+ um = [AgenorUserManager sharedUserManager];
+ uid = [[self container] login];
+ ms = [[[NSMutableString alloc] initWithCapacity:128] autorelease];
+
+ /* preamble */
+ [ms appendString:@"BEGIN:VCALENDAR\r\n"];
+ [ms appendString:@"BEGIN:VFREEBUSY\r\n"];
+ /* PRODID */
+ [ms appendString:@"PRODID:SOGo/0.9\r\n"];
+ /* VERSION */
+ [ms appendString:@"VERSION:2.0\r\n"];
+ /* DTSTAMP */
+ [ms appendString:@"DTSTAMP:"];
+ [ms appendString:[[NSCalendarDate calendarDate] icalString]];
+ [ms appendString:@"\r\n"];
+
+ /* ORGANIZER - strictly required but missing for now */
+
+ /* ATTENDEE */
+ [ms appendString:@"ATTENDEE"];
+ if ((x = [um getCNForUID:uid])) {
+ [ms appendString:@";CN=\""];
+ [ms appendString:[x iCalDQUOTESafeString]];
+ [ms appendString:@"\""];
+ }
+ if ((x = [um getEmailForUID:uid])) {
+ [ms appendString:@":"]; /* sic! */
+ [ms appendString:[x iCalSafeString]];
+ }
+ [ms appendString:@"\r\n"];
+
+ /* DTSTART */
+ [ms appendString:@"DTSTART:"];
+ [ms appendString:[_startDate icalString]];
+ [ms appendString:@"\r\n"];
+
+ /* DTEND */
+ [ms appendString:@"DTEND:"];
+ [ms appendString:[_endDate icalString]];
+ [ms appendString:@"\r\n"];
+
+ /* FREEBUSY */
+ count = [_infos count];
+ for (i = 0; i < count; i++) {
+ NSDictionary *info;
+ NSCalendarDate *startDate, *endDate;
+
+ info = [_infos objectAtIndex:i];
+ startDate = [info objectForKey:@"startDate"];
+ endDate = [info objectForKey:@"endDate"];
+
+ /* NOTE: currently we cannot differentiate between all the types defined
+ * in RFC2445, Section 4.2.9.
+ * These are: FREE" / "BUSY" / "BUSY-UNAVAILABLE" / "BUSY-TENTATIVE"
+ */
+
+ [ms appendString:@"FREEBUSY;FBTYPE=BUSY:"];
+ [ms appendString:[startDate icalString]];
+ [ms appendString:@"/"];
+ [ms appendString:[endDate icalString]];
+ [ms appendString:@"\r\n"];
+ }
+
+ /* postamble */
+ [ms appendString:@"END:VFREEBUSY\r\n"];
+ [ms appendString:@"END:VCALENDAR\r\n"];
+ return ms;
+}
+
+/* deliver content without need for view method */
+
+- (id)GETAction:(id)_ctx {
+ WOResponse *r;
+ NSData *contentData;
+
+ contentData = [[self contentAsString] dataUsingEncoding:NSUTF8StringEncoding];
+
+ r = [(WOContext *)_ctx response];
+ [r setHeader:@"text/calendar" forKey:@"content-type"];
+ [r setContent:contentData];
+ [r setStatus:200];
+ return r;
+}
+
+@end
# Version file
-SUBMINOR_VERSION:=34
+SUBMINOR_VERSION:=35
# v0.9.32 requires libGDLContentStore v4.5.26
# v0.9.28 requires libNGiCal v4.5.47
SOGoAppointmentObject = {
superclass = "SOGoContentObject";
};
+
+ SOGoFreeBusyObject = {
+ superclass = "SOGoContentObject";
+ };
};
}
/* i.e. amelie-ida01.melanie2.i2 */
- (NSString *)getServerForUID:(NSString *)_uid;
+- (NSURL *)getFreeBusyURLForUID:(NSString *)_uid;
+
@end
#endif /* __AgenorUserManager_H_ */
- (NSArray *)_serverCandidatesForMineqMelRoutage:(NGLdapAttribute *)attr {
NSMutableArray *serverCandidates;
- unsigned i, count;
-
- count = [attr count];
- serverCandidates = [NSMutableArray arrayWithCapacity:count];
- for(i = 0; i < count; i++) {
- NSRange r;
- NSString *route;
-
- route = [attr stringValueAtIndex:i];
- r = [route rangeOfString:@".melanie2.i2" options:NSBackwardsSearch];
+ unsigned i, count;
+
+ count = [attr count];
+ serverCandidates = [NSMutableArray arrayWithCapacity:count];
+ for(i = 0; i < count; i++) {
+ NSRange r;
+ NSString *route;
+
+ route = [attr stringValueAtIndex:i];
+ r = [route rangeOfString:@".melanie2.i2" options:NSBackwardsSearch];
+ if(r.length > 0) {
+ unsigned length;
+
+ /* be clever */
+ length = [route length];
+ r = NSMakeRange(0, length - r.length);
+ r = [route rangeOfString:@"@" options:NSBackwardsSearch range:r];
+ if(r.length > 0) {
+ unsigned start;
+ NSRange serverNameRange;
+
+ start = NSMaxRange(r);
+ serverNameRange = NSMakeRange(start, length - start);
+ r = NSMakeRange(0, length - start);
+ r = [route rangeOfString:@"%" options:NSBackwardsSearch range:r];
if(r.length > 0) {
- unsigned length;
-
- /* be clever */
- length = [route length];
- r = NSMakeRange(0, length - r.length);
- r = [route rangeOfString:@"@" options:NSBackwardsSearch range:r];
- if(r.length > 0) {
- unsigned start;
- NSRange serverNameRange;
-
- start = NSMaxRange(r);
- serverNameRange = NSMakeRange(start, length - start);
- r = NSMakeRange(0, length - start);
- r = [route rangeOfString:@"%" options:NSBackwardsSearch range:r];
- if(r.length > 0) {
- NSString *serverName;
-
- serverName = [route substringWithRange:serverNameRange];
- [serverCandidates addObject:serverName];
- }
- }
+ NSString *serverName;
+
+ serverName = [route substringWithRange:serverNameRange];
+ [serverCandidates addObject:serverName];
}
}
- return serverCandidates;
+ }
+ }
+ return serverCandidates;
}
- (NSString *)primaryGetServerForAgenorUID:(NSString *)_uid {
return server;
}
+- (NSURL *)getFreeBusyURLForUID:(NSString *)_uid {
+ return nil;
+}
+
/* debugging */
- (BOOL)isDebuggingEnabled {
+2005-07-06 Marcus Mueller <znek@mulle-kybernetik.com>
+
+ * v0.9.37
+
+ * SOGoUserFolder.m: added 'freebusy.ifb' as an object to the
+ collection for proper display via DAV.
+
+ * SOGoAppointmentICalRenderer.m: fixed header inclusion
+
+ * AgenorUserManager.[hm]: added proposed future API for discovering
+ URLs for free/busy information (implementation currently returns
+ nil)
+
2005-07-05 Marcus Mueller <znek@mulle-kybernetik.com>
* SOGoAppointment.m: fixed a wrong -release (v0.9.36)
#include "SOGoAppointmentICalRenderer.h"
#include "SOGoAppointment.h"
-#include "NSString+iCal.h"
#include <NGiCal/NGiCal.h>
+#include <NGiCal/iCalRenderer.h>
#include "common.h"
// TODO: the basic renderer should be part of NGiCal
- (NSString *)ocsUserPath;
- (NSString *)ocsPrivateCalendarPath;
+- (id)lookupFreeBusyObject;
+
@end
#endif /* __SOGo_SOGoUserFolder_H__ */
return [folder autorelease];
}
+- (id)freeBusyObject:(NSString *)_key inContext:(id)_ctx {
+ static Class fbClass = Nil;
+ id fb;
+
+ if (fbClass == Nil)
+ fbClass = NSClassFromString(@"SOGoFreeBusyObject");
+ if (fbClass == Nil) {
+ [self errorWithFormat:@"missing SOGoFreeBusyObject class!"];
+ return nil;
+ }
+
+ fb = [[fbClass alloc] initWithName:_key inContainer:self];
+ return [fb autorelease];
+}
+
- (id)lookupName:(NSString *)_key inContext:(id)_ctx acquire:(BOOL)_flag {
id obj;
if ([_key isEqualToString:@"Mail"])
return [self mailAccountsFolder:_key inContext:_ctx];
-
+
+ if ([_key isEqualToString:@"freebusy.ifb"])
+ return [self freeBusyObject:_key inContext:_ctx];
+
/* return 404 to stop acquisition */
return [NSException exceptionWithHTTPStatus:404 /* Not Found */];
}
/* WebDAV */
- (NSArray *)fetchContentObjectNames {
- /* the SOGoUserFolder has no 'files', only subfolders */
- return nil;
+ static NSArray *cos = nil;
+
+ if (!cos) {
+ cos = [[NSArray alloc] initWithObjects:@"freebusy.ifb", nil];
+ }
+ return cos;
}
- (BOOL)davIsCollection {
# version file
-SUBMINOR_VERSION:=36
+SUBMINOR_VERSION:=37
# v0.9.34 requires libGDLContentStore v4.5.26
# v0.9.26 requires libOGoContentStore v0.9.13
+2005-07-06 Marcus Mueller <znek@mulle-kybernetik.com>
+
+ * UIxAppointmentProposal.m: changed implementation of
+ -proposalSearchAction to use the new SOGoFreeBusyObject API
+ (v0.9.124)
+
2005-07-05 Marcus Mueller <znek@mulle-kybernetik.com>
* v0.9.123
@end
#include <SoObjects/Appointments/SOGoAppointmentFolder.h>
+#include <SoObjects/Appointments/SOGoFreeBusyObject.h>
#include <NGExtensions/NGCalendarDateRange.h>
#include <NGiCal/NGiCal.h>
#include "common.h"
}
- (id)proposalSearchAction {
- NSArray *attendees, *uids;
- SOGoAppointmentFolder *groupCalendar;
- NSArray *infos;
- NSArray *ranges;
-
+ NSArray *attendees, *uids, *fbos, *ranges;
+ unsigned i, count;
+ NSMutableArray *allInfos;
+
[self logWithFormat:@"search from %@ to %@",
[self startDate], [self endDate]];
[self logWithFormat:@"Note: no UIDs selected."];
return self;
}
-
- groupCalendar = [[self clientObject] lookupGroupCalendarFolderForUIDs:uids
- inContext:[self context]];
- [self debugWithFormat:@"group calendar: %@", groupCalendar];
-
- if (![groupCalendar respondsToSelector:@selector(fetchFreebusyInfosFrom:to:)]) {
- return [NSException exceptionWithHTTPStatus:500 /* Internal Error */
- reason:@"invalid folder to run proposal query on!"];
- }
-
- infos = [groupCalendar fetchFreebusyInfosFrom:[self startDate]
- to:[self endDate]];
- [self debugWithFormat:@" process: %d events", [infos count]];
- ranges = [infos arrayByCreatingDateRangesFromObjectsWithStartDateKey:
- @"startDate"
- andEndDateKey:@"endDate"];
+ fbos = [[self clientObject] lookupFreeBusyObjectsForUIDs:uids
+ inContext:[self context]];
+ count = [fbos count];
+ // wild guess at capacity
+ allInfos = [[[NSMutableArray alloc] initWithCapacity:count * 10] autorelease];
+
+ for (i = 0; i < count; i++) {
+ SOGoFreeBusyObject *fb;
+ NSArray *infos;
+
+ fb = [fbos objectAtIndex:i];
+ if (fb != (SOGoFreeBusyObject *)[NSNull null]) {
+ infos = [fb fetchFreebusyInfosFrom:[self startDate] to:[self endDate]];
+ [allInfos addObjectsFromArray:infos];
+ }
+ }
+ [self debugWithFormat:@" processing: %d infos", [allInfos count]];
+ ranges = [allInfos arrayByCreatingDateRangesFromObjectsWithStartDateKey:
+ @"startDate"
+ andEndDateKey:@"endDate"];
ranges = [ranges arrayByCompactingContainedDateRanges];
[self debugWithFormat:@" ranges: %@", ranges];
# Version file
-SUBMINOR_VERSION:=123
+SUBMINOR_VERSION:=124
+# v0.9.123 requires Appointments v0.9.35
# v0.9.123 requires SOGoUI v0.9.24
# v0.9.115 requires NGiCal v4.5.44
# v0.9.113 requires libSOGo v0.9.30