From 4da5306afee6f96a4dc88eaffec4bb5abed63d7e Mon Sep 17 00:00:00 2001 From: Wolfgang Sourdeau Date: Wed, 11 Oct 2006 18:48:25 +0000 Subject: [PATCH] Monotone-Parent: 62e332ed96e664a182b2a83d34f70c435afdb0c3 Monotone-Revision: 2c486a6c83c64dafd58ffc10c2c40443669357c1 Monotone-Author: wsourdeau@inverse.ca Monotone-Date: 2006-10-11T18:48:25 Monotone-Branch: ca.inverse.sogo --- ChangeLog | 11 + .../Appointments/SOGoAppointmentFolder.h | 59 +-- .../Appointments/SOGoAppointmentFolder.m | 392 +++++++++++------- 3 files changed, 295 insertions(+), 167 deletions(-) diff --git a/ChangeLog b/ChangeLog index 8ac63487a..66c838b0e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,16 @@ 2006-10-11 Wolfgang Sourdeau + * SoObjects/Appointments/SOGoAppointmentFolder.m + ([SOGoAppointmentFolder + -fetchFields:_fieldsfromFolder:_folderfrom:_startDateto:_endDatecomponent:_component]): added a "component" parameter to match the query against the specified component types. Made startDate and endDate optional by ignoring them altogether in the query whenever one of them is nil. + ([SOGoAppointmentFolder -doCalendarQuery:context]): fetch + components of type "vtodo" as well as "vevent". + ([SOGoAppointmentFolder -lookupName:inContext:acquire:]): if the + url specified ends with AsTask or AsAppointment, returns the + an object of the appropriate class, otherwise deduce it from its + content if the HTTP method is "PUT", otherwise read its type from + the quick table. + * OGoContentStore/OCSiCalFieldExtractor.m ([OCSiCalFieldExtractor -extractQuickFieldsFromTodo:_task]): extract quick fields from tasks. diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.h b/SoObjects/Appointments/SOGoAppointmentFolder.h index 853ef0dc1..7bba091bd 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.h +++ b/SoObjects/Appointments/SOGoAppointmentFolder.h @@ -48,53 +48,60 @@ /* selection */ -- (NSArray *)calendarUIDs; +- (NSArray *) calendarUIDs; /* vevent UID handling */ -- (NSString *)resourceNameForEventUID:(NSString *)_uid; +- (NSString *) resourceNameForEventUID: (NSString *) _uid; +- (Class) objectClassForResourceNamed: (NSString *) c_name; /* fetching */ -- (NSArray *)fetchFields:(NSArray *)_fields - fromFolder:(GCSFolder *)_folder - from:(NSCalendarDate *)_startDate - to:(NSCalendarDate *)_endDate; +- (NSArray *) fetchFields: (NSArray *) _fields + fromFolder: (GCSFolder *) _folder + from: (NSCalendarDate *) _startDate + to: (NSCalendarDate *) _endDate + component: (id) _component; -- (NSArray *)fetchFields:(NSArray *)_fields - from:(NSCalendarDate *)_startDate - to:(NSCalendarDate *)_endDate; +- (NSArray * ) fetchFields: (NSArray *) _fields + from: (NSCalendarDate *) _startDate + to: (NSCalendarDate *) _endDate + component: (id) _component; -- (NSArray *)fetchCoreInfosFrom:(NSCalendarDate *)_startDate - to:(NSCalendarDate *)_endDate; +- (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate + to: (NSCalendarDate *) _endDate + component: (id) _component; -- (NSArray *)fetchOverviewInfosFrom:(NSCalendarDate *)_startDate - to:(NSCalendarDate *)_endDate; - -- (NSArray *)fetchFreebusyInfosFrom:(NSCalendarDate *)_startDate - to:(NSCalendarDate *)_endDate; +- (NSArray *) fetchFreebusyInfosFrom: (NSCalendarDate *) _startDate + to: (NSCalendarDate *) _endDate; /* URL generation */ -- (NSString *)baseURLForAptWithUID:(NSString *)_uid inContext:(id)_ctx; +- (NSString *) baseURLForAptWithUID: (NSString *) _uid + inContext: (id) _ctx; /* folder management */ -- (id)lookupHomeFolderForUID:(NSString *)_uid inContext:(id)_ctx; +- (id) lookupHomeFolderForUID: (NSString *) _uid + inContext: (id) _ctx; -- (NSArray *)lookupCalendarFoldersForUIDs:(NSArray *)_uids inContext:(id)_ctx; -- (NSArray *)lookupFreeBusyObjectsForUIDs:(NSArray *)_uids 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 - inContext:(id)_ctx; +- (NSArray *) uidsFromICalPersons: (NSArray *) _persons; +- (NSArray *) lookupCalendarFoldersForICalPerson: (NSArray *) _persons + inContext: (id) _ctx; -- (id)lookupGroupFolderForUIDs:(NSArray *)_uids inContext:(id)_ctx; -- (id)lookupGroupCalendarFolderForUIDs:(NSArray *)_uids inContext:(id)_ctx; +- (id) lookupGroupFolderForUIDs: (NSArray *) _uids + inContext: (id) _ctx; +- (id) lookupGroupCalendarFolderForUIDs: (NSArray *) _uids + inContext: (id) _ctx; /* bulk fetches */ -- (NSArray *)fetchAllSOGoAppointments; +- (NSArray *) fetchAllSOGoAppointments; @end diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 63d910952..fcb30221f 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -19,16 +19,20 @@ 02111-1307, USA. */ -#include "SOGoAppointmentFolder.h" -#include -#include -#include -#include -#include -#include -#include "common.h" +#import +#import +#import +#import +#import +#import +#import "common.h" + +#import #import "SOGoAppointmentObject.h" +#import "SOGoTaskObject.h" + +#import "SOGoAppointmentFolder.h" #if APPLE_Foundation_LIBRARY || NeXT_Foundation_LIBRARY @interface NSDate(UsedPrivates) @@ -121,25 +125,7 @@ static NSNumber *sharedYes = nil; - (BOOL) isValidAppointmentName: (NSString *)_key { - if ([_key length] == 0) - return NO; - - return YES; -} - -- (id) appointmentWithName: (NSString *)_key - inContext: (id)_ctx -{ - static Class aptClass = Nil; - - if (aptClass == Nil) - aptClass = NSClassFromString(@"SOGoAppointmentObject"); - if (aptClass == Nil) { - [self errorWithFormat:@"missing SOGoAppointmentObject class!"]; - return nil; - } - - return [aptClass objectWithName: _key inContainer: self]; + return ([_key length] != 0); } - (id) lookupActionForCalDAVMethod: (NSString *)_key @@ -158,17 +144,17 @@ static NSNumber *sharedYes = nil; return invocation; } -- (void) appendAppointment: (NSDictionary *) appointment - withBaseURL: (NSString *) baseURL - toREPORTResponse: (WOResponse *) r +- (void) appendObject: (NSDictionary *) object + withBaseURL: (NSString *) baseURL + toREPORTResponse: (WOResponse *) r { - SOGoAppointmentObject *realApt; + SOGoContentObject *ocsObject; NSString *c_name, *etagLine, *dataLine; - c_name = [appointment objectForKey: @"c_name"]; + c_name = [object objectForKey: @"c_name"]; - realApt = [SOGoAppointmentObject objectWithName: c_name - inContainer: self]; + ocsObject = [SOGoContentObject objectWithName: c_name + inContainer: self]; [r appendContentString: @" \r\n"]; [r appendContentString: @" "]; @@ -181,7 +167,7 @@ static NSNumber *sharedYes = nil; [r appendContentString: @" \r\n"]; [r appendContentString: @" \r\n"]; etagLine = [NSString stringWithFormat: @" %@\r\n", - [realApt davEntityTag]]; + [ocsObject davEntityTag]]; [r appendContentString: etagLine]; [r appendContentString: @" \r\n"]; [r appendContentString: @" HTTP/1.1 200 OK\r\n"]; @@ -190,7 +176,7 @@ static NSNumber *sharedYes = nil; dataLine = [NSString stringWithFormat: @" %@\r\n", - [realApt contentAsString]]; + [ocsObject contentAsString]]; [r appendContentString: dataLine]; [r appendContentString: @" \r\n"]; @@ -201,6 +187,7 @@ static NSNumber *sharedYes = nil; WOResponse *r; NSString *baseURL, *content; NSArray *apts; + NSMutableArray *components; NSEnumerator *appointments; NSDictionary *appointment; @@ -209,12 +196,6 @@ static NSNumber *sharedYes = nil; // account otherwise all events will be returned. We should also manage the // VTODO... and well, have a clean implementation. - apts - = [self fetchCoreInfosFrom: - [NSCalendarDate dateWithTimeIntervalSince1970: 0] - to: - [NSCalendarDate dateWithTimeIntervalSince1970: 0x7fffffff]]; - baseURL = [self baseURLInContext: context]; r = [context response]; [r setStatus: 207]; @@ -229,42 +210,115 @@ static NSNumber *sharedYes = nil; content = [[NSString alloc] initWithData: [[context request] content] encoding: NSUTF8StringEncoding]; [content autorelease]; + + components = [NSMutableArray new]; if ([content indexOfString: @"VEVENT"] != NSNotFound || [content indexOfString: @"vevent"] != NSNotFound) + [components addObject: @"vevent"]; + if ([content indexOfString: @"VTODO"] != NSNotFound + || [content indexOfString: @"vtodo"] != NSNotFound) + [components addObject: @"vtodo"]; + + apts = [self fetchCoreInfosFrom: nil to: nil component: components]; + [components release]; + appointments = [apts objectEnumerator]; + appointment = [appointments nextObject]; + while (appointment) { - appointments = [apts objectEnumerator]; + [self appendObject: appointment + withBaseURL: baseURL + toREPORTResponse: r]; appointment = [appointments nextObject]; - while (appointment) - { - [self appendAppointment: appointment - withBaseURL: baseURL - toREPORTResponse: r]; - appointment = [appointments nextObject]; - } } [r appendContentString:@"\r\n"]; return r; } +- (Class) objectClassForContent: (NSString *) content +{ + iCalCalendar *calendar; + NSArray *elements; + NSString *firstTag; + Class objectClass; + + objectClass = Nil; + + calendar = [iCalCalendar parseSingleFromSource: content]; + if (calendar) + { + elements = [calendar allObjects]; + if ([elements count]) + { + firstTag = [[[elements objectAtIndex: 0] tag] uppercaseString]; + if ([firstTag isEqualToString: @"VEVENT"]) + objectClass = [SOGoAppointmentObject class]; + else if ([firstTag isEqualToString: @"VTODO"]) + objectClass = [SOGoTaskObject class]; + } + } + + return objectClass; +} + +- (id) deduceObjectForName: (NSString *)_key + inContext: (id)_ctx +{ + WORequest *request; + NSString *method; + Class objectClass; + id obj; + + request = [_ctx request]; + method = [request method]; + if ([method isEqualToString: @"PUT"]) + objectClass = [self objectClassForContent: [request contentAsString]]; + else + objectClass = [self objectClassForResourceNamed: _key]; + + if (objectClass) + obj = [objectClass objectWithName: _key inContainer: self]; + else + obj = nil; + + return obj; +} + - (id) lookupName: (NSString *)_key inContext: (id)_ctx acquire: (BOOL)_flag { id obj; - - /* first check attributes directly bound to the application */ - if ((obj = [super lookupName:_key inContext:_ctx acquire:NO])) - return obj; - - if ([_key hasPrefix: @"{urn:ietf:params:xml:ns:caldav}"]) - return [self lookupActionForCalDAVMethod: [_key substringFromIndex: 31]]; + NSString *url; - if ([self isValidAppointmentName:_key]) - return [self appointmentWithName:_key inContext:_ctx]; - - /* return 404 to stop acquisition */ - return [NSException exceptionWithHTTPStatus:404 /* Not Found */]; + NSLog (@"lookup name '%@' in apt folder", _key); + + /* first check attributes directly bound to the application */ + obj = [super lookupName:_key inContext:_ctx acquire:NO]; + if (!obj) + { + if ([_key hasPrefix: @"{urn:ietf:params:xml:ns:caldav}"]) + obj + = [self lookupActionForCalDAVMethod: [_key substringFromIndex: 31]]; + else if ([self isValidAppointmentName:_key]) + { + url = [[[_ctx request] uri] urlWithoutParameters]; + if ([url hasSuffix: @"AsTask"]) + obj = [SOGoTaskObject objectWithName: _key + inContainer: self]; + else if ([url hasSuffix: @"AsAppointment"]) + obj = [SOGoAppointmentObject objectWithName: _key + inContainer: self]; + else + obj = [self deduceObjectForName: _key + inContext: _ctx]; + } + } + + if (!obj) + obj = [NSException exceptionWithHTTPStatus:404 /* Not Found */]; + + return obj; } /* vevent UID handling */ @@ -327,6 +381,33 @@ static NSNumber *sharedYes = nil; return rname; } +- (Class) objectClassForResourceNamed: (NSString *) c_name +{ + EOQualifier *qualifier; + NSArray *records; + NSString *component; + Class objectClass; + + qualifier = [EOQualifier qualifierWithQualifierFormat:@"c_name = %@", c_name]; + records = [[self ocsFolder] fetchFields: [NSArray arrayWithObject: @"component"] + matchingQualifier: qualifier]; + + if ([records count]) + { + component = [[records objectAtIndex:0] valueForKey: @"component"]; + if ([component isEqualToString: @"vevent"]) + objectClass = [SOGoAppointmentObject class]; + else if ([component isEqualToString: @"vtodo"]) + objectClass = [SOGoTaskObject class]; + else + objectClass = Nil; + } + else + objectClass = Nil; + + return objectClass; +} + /* fetching */ - (NSMutableDictionary *) fixupRecord: (NSDictionary *) _record @@ -396,7 +477,7 @@ static NSNumber *sharedYes = nil; return; } - row = [self fixupRecord:_row fetchRange:_r]; + row = [self fixupRecord:_row fetchRange: _r]; [row removeObjectForKey:@"cycleinfo"]; [row setObject:sharedYes forKey:@"isRecurrentEvent"]; @@ -467,15 +548,38 @@ static NSNumber *sharedYes = nil; return ma; } +- (NSString *) _sqlStringForComponent: (id) _component +{ + NSString *sqlString; + NSArray *components; + + if (_component) + { + if ([_component isKindOfClass: [NSArray class]]) + components = _component; + else + components = [NSArray arrayWithObject: _component]; + + sqlString + = [NSString stringWithFormat: @" AND (component = '%@')", + [components componentsJoinedByString: @"' OR component = '"]]; + } + else + sqlString = @""; + + return sqlString; +} + - (NSArray *) fetchFields: (NSArray *) _fields fromFolder: (GCSFolder *) _folder from: (NSCalendarDate *) _startDate to: (NSCalendarDate *) _endDate + component: (id) _component { - EOQualifier *qualifier; - NSMutableArray *fields, *ma = nil; - NSArray *records; - NSString *sql; + EOQualifier *qualifier; + NSMutableArray *fields, *ma = nil; + NSArray *records; + NSString *sql, *dateSqlString, *componentSqlString; NGCalendarDateRange *r; if (_folder == nil) { @@ -484,65 +588,84 @@ static NSNumber *sharedYes = nil; return nil; } - r = [NGCalendarDateRange calendarDateRangeWithStartDate:_startDate - endDate:_endDate]; + if (_startDate && _endDate) + { + r = [NGCalendarDateRange calendarDateRangeWithStartDate: _startDate + endDate: _endDate]; + dateSqlString + = [NSString stringWithFormat: @" AND (startdate > %d) AND (enddate < %d)", + (unsigned int) [_startDate timeIntervalSince1970], + (unsigned int) [_endDate timeIntervalSince1970]]; + } + else + { + r = nil; + dateSqlString = @""; + } + + componentSqlString = [self _sqlStringForComponent: _component]; /* prepare mandatory fields */ - fields = [NSMutableArray arrayWithArray:_fields]; - [fields addObject:@"uid"]; - [fields addObject:@"startdate"]; - [fields addObject:@"enddate"]; - + fields = [NSMutableArray arrayWithArray: _fields]; + [fields addObject: @"uid"]; + [fields addObject: @"startdate"]; + [fields addObject: @"enddate"]; + if (logger) [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate]; - - sql = [NSString stringWithFormat:@"(startdate < %d) AND (enddate > %d)" - @" AND (iscycle = 0)", - (unsigned int)[_endDate timeIntervalSince1970], - (unsigned int)[_startDate timeIntervalSince1970]]; + + sql = [NSString stringWithFormat: @"(iscycle = 0)%@%@", + dateSqlString, componentSqlString]; /* fetch non-recurrent apts first */ - qualifier = [EOQualifier qualifierWithQualifierFormat:sql]; + qualifier = [EOQualifier qualifierWithQualifierFormat: sql]; + + records = [_folder fetchFields: fields matchingQualifier: qualifier]; + if (records) + { + if (r) + records = [self fixupRecords: records fetchRange: r]; + if (logger) + [self debugWithFormat: @"fetched %i records: %@", + [records count], records]; + ma = [NSMutableArray arrayWithArray: records]; + } - records = [_folder fetchFields:fields matchingQualifier:qualifier]; - if (records != nil) { - records = [self fixupRecords:records fetchRange:r]; - if (logger) - [self debugWithFormat:@"fetched %i records: %@",[records count],records]; - ma = [NSMutableArray arrayWithArray:records]; - } - /* fetch recurrent apts now */ - sql = [NSString stringWithFormat:@"(startdate < %d) AND (cycleenddate > %d)" - @" AND (iscycle = 1)", - (unsigned int)[_endDate timeIntervalSince1970], - (unsigned int)[_startDate timeIntervalSince1970]]; - qualifier = [EOQualifier qualifierWithQualifierFormat:sql]; + sql = [NSString stringWithFormat: @"(iscycle = 1)%@%@", + dateSqlString, componentSqlString]; + qualifier = [EOQualifier qualifierWithQualifierFormat: sql]; - [fields addObject:@"cycleinfo"]; + [fields addObject: @"cycleinfo"]; + + records = [_folder fetchFields: fields matchingQualifier: qualifier]; + if (records) + { + if (logger) + [self debugWithFormat: @"fetched %i cyclic records: %@", + [records count], records]; + if (r) + records = [self fixupCyclicRecords: records fetchRange: r]; + if (!ma) + ma = [NSMutableArray arrayWithCapacity: [records count]]; + [ma addObjectsFromArray: records]; + } + else if (!ma) + { + [self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__]; + return nil; + } - records = [_folder fetchFields:fields matchingQualifier:qualifier]; - if (records != nil) { - if (logger) - [self debugWithFormat:@"fetched %i cyclic records: %@", - [records count], records]; - records = [self fixupCyclicRecords:records fetchRange:r]; - if (!ma) ma = [NSMutableArray arrayWithCapacity:[records count]]; - [ma addObjectsFromArray:records]; - } - else if (ma == nil) { - [self errorWithFormat:@"(%s): fetch failed!", __PRETTY_FUNCTION__]; - return nil; - } /* NOTE: why do we sort here? This probably belongs to UI but cannot be achieved as fast there as we can do it here because we're operating on a mutable array - having the apts sorted is never a bad idea, though */ - [ma sortUsingSelector:@selector(compareAptsAscending:)]; + [ma sortUsingSelector: @selector (compareAptsAscending:)]; if (logger) [self debugWithFormat:@"returning %i records", [ma count]]; + return ma; } @@ -550,6 +673,7 @@ static NSNumber *sharedYes = nil; - (NSArray *) fetchFields: (NSArray *) _fields from: (NSCalendarDate *) _startDate to: (NSCalendarDate *) _endDate + component: (id) _component { GCSFolder *folder; @@ -558,8 +682,10 @@ static NSNumber *sharedYes = nil; __PRETTY_FUNCTION__]; return nil; } - return [self fetchFields:_fields fromFolder:folder - from:_startDate to:_endDate]; + + return [self fetchFields: _fields fromFolder: folder + from: _startDate to: _endDate + component: _component]; } @@ -570,44 +696,28 @@ static NSNumber *sharedYes = nil; if (infos == nil) { infos = [[NSArray alloc] initWithObjects:@"partmails", @"partstates", nil]; } - return [self fetchFields:infos from:_startDate to:_endDate]; + return [self fetchFields: infos + from: _startDate to: _endDate + component: nil]; } -- (NSArray *) fetchOverviewInfosFrom: (NSCalendarDate *) _startDate - to: (NSCalendarDate *) _endDate -{ - static NSArray *infos = nil; // TODO: move to a plist file - if (infos == nil) { - infos = [[NSArray alloc] initWithObjects: - @"c_name", @"title", - @"location", @"orgmail", @"status", @"ispublic", - @"isallday", @"priority", - @"partmails", @"partstates", - nil]; - } - return [self fetchFields:infos - from:_startDate - to:_endDate]; -} - - (NSArray *) fetchCoreInfosFrom: (NSCalendarDate *) _startDate - to: (NSCalendarDate *) _endDate + to: (NSCalendarDate *) _endDate + component: (id) _component { static NSArray *infos = nil; // TODO: move to a plist file - if (infos == nil) { - infos = [[NSArray alloc] initWithObjects: - @"c_name", - @"title", @"location", @"orgmail", - @"status", @"ispublic", - @"isallday", @"isopaque", - @"participants", @"partmails", - @"partstates", @"sequence", @"priority", nil]; - } - return [self fetchFields:infos - from:_startDate - to:_endDate]; + if (!infos) + infos = [[NSArray alloc] initWithObjects: + @"c_name", @"component", + @"title", @"location", @"orgmail", + @"status", @"ispublic", + @"isallday", @"isopaque", + @"participants", @"partmails", + @"partstates", @"sequence", @"priority", nil]; + + return [self fetchFields: infos from: _startDate to: _endDate component: _component]; } /* URL generation */