From 41dff61831dbb9060977ffe9326ef8bde050e1d4 Mon Sep 17 00:00:00 2001 From: znek Date: Tue, 15 Feb 2005 21:49:57 +0000 Subject: [PATCH] first working fix for SOGo Bugs #966/#967 git-svn-id: http://svn.opengroupware.org/SOGo/trunk@569 d1b88da0-ebda-0310-925b-ed51d893ca5b --- .../Appointments/SOGoAppointmentFolder.m | 107 ++++++++--- .../Appointments/SOGoAppointmentObject.h | 3 +- .../Appointments/SOGoAppointmentObject.m | 46 +++++ SOGo/SoObjects/ChangeLog | 20 +++ SOGo/SoObjects/SOGo/GNUmakefile.preamble | 1 + SOGo/UI/Scheduler/ChangeLog | 24 +++ .../Scheduler/English.lproj/default.strings | 12 ++ SOGo/UI/Scheduler/GNUmakefile | 1 + SOGo/UI/Scheduler/UIxAppointmentEditor.m | 170 ++++++++++++++++-- SOGo/UI/Scheduler/UIxAppointmentEditor.wox | 38 ++++ SOGo/UI/Scheduler/UIxCalView.m | 83 ++------- SOGo/UI/Scheduler/UIxDatePicker.m | 2 +- SOGo/UI/Scheduler/Version | 3 +- SOGo/UI/Scheduler/cycles.plist | 29 +++ SOGo/UI/Scheduler/product.plist | 1 + SOGoLogic/ChangeLog | 9 + SOGoLogic/SOGoAppointment.h | 11 +- SOGoLogic/SOGoAppointment.m | 28 +++ SOGoLogic/SOGoAppointmentICalRenderer.m | 7 + SOGoLogic/Version | 3 +- 20 files changed, 473 insertions(+), 125 deletions(-) create mode 100644 SOGo/UI/Scheduler/cycles.plist diff --git a/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m b/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m index cf3df43b..4c71f256 100644 --- a/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SOGo/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -24,7 +24,9 @@ #include #include #include +#include #include +#include #include "common.h" #include #include @@ -38,10 +40,15 @@ @implementation SOGoAppointmentFolder static BOOL debugOn = NO; -static NSTimeZone *MET = nil; +static NSTimeZone *MET = nil; + (void)initialize { - if (MET == nil) MET = [[NSTimeZone timeZoneWithAbbreviation:@"MET"] retain]; + static BOOL didInit = NO; + + if (didInit) return; + didInit = YES; + + MET = [[NSTimeZone timeZoneWithAbbreviation:@"MET"] retain]; } + (NSString *)globallyUniqueObjectId { @@ -126,36 +133,50 @@ static NSTimeZone *MET = nil; /* fetching */ -- (NSMutableDictionary *)fixupRecord:(NSDictionary *)_record { +- (NSMutableDictionary *)fixupRecord:(NSDictionary *)_record + fetchRange:(NGCalendarDateRange *)_r +{ NSMutableDictionary *md; id tmp; md = [[_record mutableCopy] autorelease]; - - if ((tmp = [_record objectForKey:@"startdate"])) { - tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970: + + if (![[_record valueForKey:@"iscycle"] intValue]) { + if ((tmp = [_record objectForKey:@"startdate"])) { + tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970: (NSTimeInterval)[tmp unsignedIntValue]]; - [tmp setTimeZone:[self viewTimeZone]]; - if (tmp) [md setObject:tmp forKey:@"startDate"]; - [tmp release]; - } - else - [self logWithFormat:@"missing 'startdate' in record?"]; - - if ((tmp = [_record objectForKey:@"enddate"])) { - tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970: + [tmp setTimeZone:[self viewTimeZone]]; + if (tmp) [md setObject:tmp forKey:@"startDate"]; + [tmp release]; + } + else + [self logWithFormat:@"missing 'startdate' in record?"]; + + if ((tmp = [_record objectForKey:@"enddate"])) { + tmp = [[NSCalendarDate alloc] initWithTimeIntervalSince1970: (NSTimeInterval)[tmp unsignedIntValue]]; + [tmp setTimeZone:[self viewTimeZone]]; + if (tmp) [md setObject:tmp forKey:@"endDate"]; + [tmp release]; + } + else + [self logWithFormat:@"missing 'enddate' in record?"]; + } + else { + /* cycle is in _r */ + tmp = [_r startDate]; + [tmp setTimeZone:[self viewTimeZone]]; + [md setObject:tmp forKey:@"startDate"]; + tmp = [_r endDate]; [tmp setTimeZone:[self viewTimeZone]]; - if (tmp) [md setObject:tmp forKey:@"endDate"]; - [tmp release]; + [md setObject:tmp forKey:@"endDate"]; } - else - [self logWithFormat:@"missing 'enddate' in record?"]; - return md; } -- (NSArray *)fixupRecords:(NSArray *)_records { +- (NSArray *)fixupRecords:(NSArray *)_records + fetchRange:(NGCalendarDateRange *)_r +{ NSMutableArray *ma; unsigned i, count; @@ -166,9 +187,33 @@ static NSTimeZone *MET = nil; ma = [NSMutableArray arrayWithCapacity:count]; for (i = 0; i < count; i++) { id row; - - row = [self fixupRecord:[_records objectAtIndex:i]]; - if (row) [ma addObject:row]; + + row = [_records objectAtIndex:i]; + if ([[row valueForKey:@"iscycle"] intValue] != 0) { + NSString *uid; + id aptObject; + iCalEvent *apt; + NSArray *ranges; + unsigned k, rCount; + + uid = [row valueForKey:@"uid"]; + aptObject = [self appointmentWithName:uid inContext:nil]; + apt = [aptObject event]; + ranges = [apt recurrenceRangesWithinCalendarDateRange:_r]; + rCount = [ranges count]; + for (k = 0; k < rCount; k++) { + NGCalendarDateRange *rRange; + id fixedRow; + + rRange = [ranges objectAtIndex:k]; + fixedRow = [self fixupRecord:row fetchRange:rRange]; + if (fixedRow) [ma addObject:fixedRow]; + } + } + else { + row = [self fixupRecord:row fetchRange:_r]; + if (row) [ma addObject:row]; + } } return ma; } @@ -179,16 +224,20 @@ static NSTimeZone *MET = nil; from:(NSCalendarDate *)_startDate to:(NSCalendarDate *)_endDate { - EOQualifier *qualifier; - NSArray *records; - NSString *sql; - + EOQualifier *qualifier; + NSArray *records; + NSString *sql; + NGCalendarDateRange *r; + if (_folder == nil) { [self errorWithFormat:@"(%s): missing folder for fetch!", __PRETTY_FUNCTION__]; return nil; } + r = [NGCalendarDateRange calendarDateRangeWithStartDate:_startDate + endDate:_endDate]; + if (debugOn) [self debugWithFormat:@"should fetch (%@=>%@) ...", _startDate, _endDate]; @@ -203,7 +252,7 @@ static NSTimeZone *MET = nil; return nil; } - records = [self fixupRecords:records]; + records = [self fixupRecords:records fetchRange:r]; if (debugOn) [self logWithFormat:@"fetched %i records: %@", [records count], records]; return records; diff --git a/SOGo/SoObjects/Appointments/SOGoAppointmentObject.h b/SOGo/SoObjects/Appointments/SOGoAppointmentObject.h index feb854d5..e7cedb72 100644 --- a/SOGo/SoObjects/Appointments/SOGoAppointmentObject.h +++ b/SOGo/SoObjects/Appointments/SOGoAppointmentObject.h @@ -36,7 +36,7 @@ appointments with an externally generated unique key. */ -@class NSString, NSArray, NSException; +@class NSString, NSArray, NSException, iCalEvent; @interface SOGoAppointmentObject : SOGoContentObject { @@ -45,6 +45,7 @@ /* accessors */ - (NSString *)iCalString; +- (iCalEvent *)event; /* folder management */ diff --git a/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m b/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m index 2a10db18..d182a0c1 100644 --- a/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SOGo/SoObjects/Appointments/SOGoAppointmentObject.m @@ -22,11 +22,40 @@ #include "SOGoAppointmentObject.h" #include +#include #include #include "common.h" @implementation SOGoAppointmentObject +static id parser = nil; +static SaxObjectDecoder *sax = nil; +static NGLogger *logger = nil; + ++ (void)initialize { + NGLoggerManager *lm; + SaxXMLReaderFactory *factory; + static BOOL didInit = NO; + + if (didInit) return; + didInit = YES; + + lm = [NGLoggerManager defaultLoggerManager]; + logger = [lm loggerForClass:self]; + + factory = [SaxXMLReaderFactory standardXMLReaderFactory]; + parser = [[factory createXMLReaderForMimeType:@"text/calendar"] + retain]; + if (parser == nil) + [logger fatalWithFormat:@"did not find a parser for text/calendar!"]; + sax = [[SaxObjectDecoder alloc] initWithMappingNamed:@"NGiCal"]; + if (sax == nil) + [logger fatalWithFormat:@"could not create the iCal SAX handler!"]; + + [parser setContentHandler:sax]; + [parser setErrorHandler:sax]; +} + - (void)dealloc { [super dealloc]; } @@ -38,6 +67,23 @@ return [self contentAsString]; } +- (iCalEvent *)event { + NSString *iCalString; + iCalEvent *event; + + iCalString = [self iCalString]; + if ([iCalString length] > 0) { + iCalCalendar *cal; + + [parser parseFromSource:iCalString]; + cal = [sax rootObject]; + [sax reset]; + event = [[cal events] lastObject]; + return event; + } + return nil; +} + /* iCal handling */ - (NSArray *)attendeeUIDsFromAppointment:(SOGoAppointment *)_apt { diff --git a/SOGo/SoObjects/ChangeLog b/SOGo/SoObjects/ChangeLog index 2fe96be7..13446336 100644 --- a/SOGo/SoObjects/ChangeLog +++ b/SOGo/SoObjects/ChangeLog @@ -1,3 +1,23 @@ +2005-02-15 Marcus Mueller + + * v0.9.25 + + * GNUmakefile.preamble: added libNGiCal as a dependency + + * Appointments/SOGoAppointmentObject.[hm]: added -event as a + convenience to retrieve a complete iCalEvent from the stored + representation. This is used by SOGoAppointmentFolder to perform + necessary range calculations on recurrent events. + + * Appointments/SOGoAppointmentFolder.[hm]: extended -fixupRecord: and + -fixupRecords: to accept a fetchRange: as second parameter. This + is used for cyclic (recurrent) events to determine the required + 'amount' of fixup that needs to be done. During -fixupRecords: its + correctly determined now whether the record describes a recurrent + event - if that's the case, the whole event is retrieved and the + record gets duplicated (memory efficiently) as necessary to resemble + the appropriate fetch information in the desired range. + 2004-12-22 Marcus Mueller * v0.9.24 diff --git a/SOGo/SoObjects/SOGo/GNUmakefile.preamble b/SOGo/SoObjects/SOGo/GNUmakefile.preamble index d1cfafc5..9d55e543 100644 --- a/SOGo/SoObjects/SOGo/GNUmakefile.preamble +++ b/SOGo/SoObjects/SOGo/GNUmakefile.preamble @@ -18,6 +18,7 @@ libSOGo_LIBRARIES_DEPEND_UPON += \ -lOGoContentStore \ -lGDLAccess \ -lNGObjWeb \ + -lNGiCal \ -lNGMime \ -lNGStreams -lNGExtensions -lEOControl \ -lXmlRpc -lDOM -lSaxObjC diff --git a/SOGo/UI/Scheduler/ChangeLog b/SOGo/UI/Scheduler/ChangeLog index 5de688c4..138177d6 100644 --- a/SOGo/UI/Scheduler/ChangeLog +++ b/SOGo/UI/Scheduler/ChangeLog @@ -1,3 +1,27 @@ +2005-02-15 Marcus Mueller + + * v0.9.113 + + * UIxCalView.m: removed dead code + +2005-02-12 Marcus Mueller + + * v0.9.112 + + * UIxAppointmentEditor.[wox,m]: added recurrence selection/display. + The current UI is similar to that found in OGo, but inappropriate in + the context of SOGo (SOGo in theory supports all recurrence rules + described in RFC2445, thus needs a more complex UI in order to render + all rules appropriately) + + * cycles.plist: property list with predefined recurrence rules for + the UIxAppointmentEditor + + * English.lproj/default.strings: new labels for cycles and accompanied + UI + + * UIxDatePicker.m: Bugfix for format edge case (when date is nil) + 2005-01-26 Marcus Mueller * UIxDatePicker.m: corrected dateFormats for French locale. The diff --git a/SOGo/UI/Scheduler/English.lproj/default.strings b/SOGo/UI/Scheduler/English.lproj/default.strings index fb0a5659..ab529c9e 100644 --- a/SOGo/UI/Scheduler/English.lproj/default.strings +++ b/SOGo/UI/Scheduler/English.lproj/default.strings @@ -119,6 +119,8 @@ "Status" = "Status"; "Location" = "Location"; "Priority" = "Priority"; +"Cycle" = "Cycle"; +"Cycle End" = "Cycle End"; "Categories" = "Categories"; "Classification" = "Classification"; "Duration" = "Duration"; @@ -183,6 +185,16 @@ "prio_8" = "Low"; "prio_9" = "Low"; +/* Cycles */ + +"cycle_once" = "once"; +"cycle_daily" = "daily"; +"cycle_weekly" = "weekly"; +"cycle_2weeks" = "all 2 weeks"; +"cycle_4weeks" = "all 4 weeks"; +"cycle_monthly" = "monthly"; +"cycle_weekday" = "weekday"; +"cycle_yearly" = "yearly"; /* Appointment categories */ diff --git a/SOGo/UI/Scheduler/GNUmakefile b/SOGo/UI/Scheduler/GNUmakefile index 49065c1a..51c7ae6c 100644 --- a/SOGo/UI/Scheduler/GNUmakefile +++ b/SOGo/UI/Scheduler/GNUmakefile @@ -110,6 +110,7 @@ SchedulerUI_LOCALIZED_RESOURCE_FILES += \ SchedulerUI_RESOURCE_FILES += \ skycalendar.html \ skycalendar.js \ + cycles.plist \ # make diff --git a/SOGo/UI/Scheduler/UIxAppointmentEditor.m b/SOGo/UI/Scheduler/UIxAppointmentEditor.m index 67af9b86..402e1389 100644 --- a/SOGo/UI/Scheduler/UIxAppointmentEditor.m +++ b/SOGo/UI/Scheduler/UIxAppointmentEditor.m @@ -26,6 +26,7 @@ @class NSString; @class iCalPerson; +@class iCalRecurrenceRule; @class SOGoAppointment; @interface UIxAppointmentEditor : UIxComponent @@ -37,6 +38,7 @@ /* individual values */ NSCalendarDate *startDate; NSCalendarDate *endDate; + NSCalendarDate *cycleUntilDate; NSString *title; NSString *location; NSString *comment; @@ -47,6 +49,7 @@ NSString *accessClass; BOOL isPrivate; /* default: NO */ BOOL checkForConflicts; /* default: NO */ + NSDictionary *cycle; } - (NSString *)iCalStringTemplate; @@ -57,7 +60,12 @@ - (void)setCheckForConflicts:(BOOL)_checkForConflicts; - (BOOL)checkForConflicts; - + +- (BOOL)hasCycle; +- (iCalRecurrenceRule *)rrule; +- (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule; +- (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule; + - (NSString *)_completeURIForMethod:(NSString *)_method; - (NSArray *)getICalPersonsFromFormValues:(NSArray *)_values @@ -85,6 +93,10 @@ #include "iCalPerson+UIx.h" #include "UIxComponent+Agenor.h" +@interface iCalRecurrenceRule (SOGoExtensions) +- (NSString *)cycleRepresentationForSOGo; +@end + @interface NSDate(UsedPrivates) - (NSString *)icalString; // TODO: this is in NGiCal @end @@ -101,20 +113,22 @@ } - (void)dealloc { - [self->iCalString release]; - [self->errorText release]; - [self->item release]; - - [self->startDate release]; - [self->endDate release]; - [self->title release]; - [self->location release]; - [self->comment release]; - [self->participants release]; - [self->resources release]; - [self->priority release]; - [self->categories release]; - [self->accessClass release]; + [self->iCalString release]; + [self->errorText release]; + [self->item release]; + + [self->startDate release]; + [self->endDate release]; + [self->cycleUntilDate release]; + [self->title release]; + [self->location release]; + [self->comment release]; + [self->participants release]; + [self->resources release]; + [self->priority release]; + [self->categories release]; + [self->accessClass release]; + [self->cycle release]; [super dealloc]; } @@ -294,6 +308,100 @@ return self->checkForConflicts; } +- (NSArray *)cycles { + static NSArray *cycles = nil; + + if (!cycles) { + NSBundle *bundle; + NSString *path; + + bundle = [NSBundle bundleForClass:[self class]]; + path = [bundle pathForResource:@"cycles" ofType:@"plist"]; + NSAssert(path != nil, @"Cannot find cycles.plist!"); + cycles = [[NSArray arrayWithContentsOfFile:path] retain]; + NSAssert(cycles != nil, @"Cannot instantiate cycles from cycles.plist!"); + } + return cycles; +} + +- (void)setCycle:(NSDictionary *)_cycle { + ASSIGN(self->cycle, _cycle); +} +- (NSDictionary *)cycle { + return self->cycle; +} +- (BOOL)hasCycle { + [self debugWithFormat:@"cycle: %@", self->cycle]; + if (![self->cycle objectForKey:@"rule"]) + return NO; + return YES; +} +- (NSString *)cycleLabel { + NSString *key; + + key = [self->item objectForKey:@"label"]; + return [self labelForKey:key]; +} + +- (iCalRecurrenceRule *)rrule { + NSString *ruleRep; + iCalRecurrenceRule *rule; + + if (![self hasCycle]) + return nil; + ruleRep = [self->cycle objectForKey:@"rule"]; + rule = [iCalRecurrenceRule recurrenceRuleWithICalRepresentation:ruleRep]; + + if (self->cycleUntilDate) + [rule setUntilDate:self->cycleUntilDate]; + return rule; +} + +- (void)adjustCycleControlsForRRule:(iCalRecurrenceRule *)_rrule { + NSDictionary *c; + + c = [self cycleMatchingRRule:_rrule]; + [self setCycle:c]; + + [self->cycleUntilDate release]; + self->cycleUntilDate = [[_rrule untilDate] copy]; + [self->cycleUntilDate setTimeZone:[self viewTimeZone]]; +} + +/* + This method is necessary, because we have a fixed sets of cycles in the UI. + The model is able to represent arbitrary rules, however. + There SHOULD be a different UI, similar to iCal.app, to allow modelling + of more complex rules. + + This method obviously cannot map all existing rules back to the fixed list + in cycles.plist. This should be fixed in a future version when interop + becomes more important. + */ +- (NSDictionary *)cycleMatchingRRule:(iCalRecurrenceRule *)_rrule { + NSString *cycleRep; + NSArray *cycles; + unsigned i, count; + + if (!_rrule) + return [[self cycles] objectAtIndex:0]; + + cycleRep = [_rrule cycleRepresentationForSOGo]; + cycles = [self cycles]; + count = [cycles count]; + for (i = 1; i < count; i++) { + NSDictionary *c; + NSString *cr; + + c = [cycles objectAtIndex:i]; + cr = [c objectForKey:@"rule"]; + if ([cr isEqualToString:cycleRep]) + return c; + } + [self warnWithFormat:@"No default cycle for rrule found! -> %@", _rrule]; + return nil; +} + /* transparency */ @@ -570,7 +678,8 @@ } - (void)loadValuesFromAppointment:(SOGoAppointment *)_appointment { - NSString *s; + NSString *s; + iCalRecurrenceRule *rrule; if ((self->startDate = [[_appointment startDate] copy]) == nil) self->startDate = [[[NSCalendarDate date] hour:11 minute:0] copy]; @@ -594,6 +703,10 @@ [self setIsPrivate:NO]; else [self setIsPrivate:YES]; /* we're possibly loosing information here */ + + /* cycles */ + rrule = [_appointment recurrenceRule]; + [self adjustCycleControlsForRRule:rrule]; } - (void)saveValuesIntoAppointment:(SOGoAppointment *)_appointment { @@ -621,9 +734,8 @@ } [_appointment setAttendees:attendees]; -#if 0 - [_appointment setOrganizer:[self getOrganizer]]; -#endif + /* cycles */ + [_appointment setRecurrenceRule:[self rrule]]; } - (void)loadValuesFromICalString:(NSString *)_ical { @@ -867,3 +979,23 @@ } @end /* UIxAppointmentEditor */ + +@interface iCalRecurrenceRule (UsedPrivates) +- (NSString *)freq; +@end /* iCalRecurrenceRule (UsedPrivates) */ + +@implementation iCalRecurrenceRule (SOGoExtensions) + +- (NSString *)cycleRepresentationForSOGo { + NSMutableString *s; + + s = [NSMutableString stringWithCapacity:20]; + [s appendString:@"FREQ="]; + [s appendString:[self freq]]; + if ([self repeatInterval] != 1) { + [s appendFormat:@";INTERVAL=%d", [self repeatInterval]]; + } + return s; +} + +@end /* iCalRecurrenceRule (SOGoExtensions) */ diff --git a/SOGo/UI/Scheduler/UIxAppointmentEditor.wox b/SOGo/UI/Scheduler/UIxAppointmentEditor.wox index b05ca925..aea0af04 100644 --- a/SOGo/UI/Scheduler/UIxAppointmentEditor.wox +++ b/SOGo/UI/Scheduler/UIxAppointmentEditor.wox @@ -117,6 +117,44 @@ + + + + : + + + + + + + + + + +
+ + : + + +
+
+ + diff --git a/SOGo/UI/Scheduler/UIxCalView.m b/SOGo/UI/Scheduler/UIxCalView.m index 6d13a146..516fa638 100644 --- a/SOGo/UI/Scheduler/UIxCalView.m +++ b/SOGo/UI/Scheduler/UIxCalView.m @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include "UIxComponent+Agenor.h" @interface UIxCalView (PrivateAPI) @@ -315,6 +317,7 @@ static BOOL shouldDisplayWeekend = NO; queryString:nil]; } + /* fetching */ - (NSCalendarDate *)startDate { @@ -324,82 +327,20 @@ static BOOL shouldDisplayWeekend = NO; return [[self startDate] tomorrow]; } -- (NSArray *)_fetchCoreInfosForUIDs:(NSArray *)_uids - uniqueWithSet:(NSMutableSet *)_uniquer -{ - NSMutableArray *ma; - unsigned i, count; - - count = [_uids count]; - ma = [NSMutableArray arrayWithCapacity:count * 10]; - - for (i = 0; i < count; i++) { - SOGoAppointmentFolder *folder; - NSArray *res; - int j, rcount; - - folder = [[self clientObject] lookupGroupCalendarFolderForUIDs:_uids - inContext:[self context]]; - res = [folder fetchOverviewInfosFrom:[self startDate] - to:[self endDate]]; - - /* perform uniquing */ - for (j = 0, rcount = [res count]; j < rcount; j++) { - NSDictionary *record; - - record = [res objectAtIndex:j]; - - if ([_uniquer containsObject:[record valueForKey:@"uid"]]) - continue; - - [ma addObject:record]; - [_uniquer addObject:[record valueForKey:@"uid"]]; - } - } - return ma; -} - - (NSArray *)fetchCoreInfos { - id aptFolder; - NSArray *more; - id uids; - - if (self->appointments) - return self->appointments; - - aptFolder = [self clientObject]; - self->appointments = - [[aptFolder fetchOverviewInfosFrom:[self startDate] - to:[self endDate]] retain]; - - /* ZNeK: this is dead code, isn't it? -> check */ - uids = [[[[self context] request] formValueForKey:@"uids"] stringValue]; - uids = [uids length] > 0 ? [uids componentsSeparatedByString:@","] : nil; - if ([uids count] > 0) { - NSMutableSet *availUIDs; - id tmp; - - [self errorWithFormat:@"fetchCoreInfos called for 'uids' form value"]; - - /* make appointments unique, prefer the own "versions" */ - tmp = [self->appointments valueForKey:@"uid"]; - availUIDs = [[NSMutableSet alloc] initWithArray:tmp]; - - more = [self _fetchCoreInfosForUIDs:uids uniqueWithSet:availUIDs]; - if (more > 0) { - NSArray *tmp; - - tmp = self->appointments; - self->appointments = [[tmp arrayByAddingObjectsFromArray:more] retain]; - [tmp release]; - } - - [availUIDs release]; + if (!self->appointments) { + id folder; + NSCalendarDate *sd, *ed; + + folder = [self clientObject]; + sd = [self startDate]; + ed = [self endDate]; + self->appointments = [[folder fetchOverviewInfosFrom:sd to:ed] retain]; } - return self->appointments; } + /* date selection & conversion */ - (NSDictionary *)todayQueryParameters { diff --git a/SOGo/UI/Scheduler/UIxDatePicker.m b/SOGo/UI/Scheduler/UIxDatePicker.m index 57d49c7d..65f94a01 100644 --- a/SOGo/UI/Scheduler/UIxDatePicker.m +++ b/SOGo/UI/Scheduler/UIxDatePicker.m @@ -112,7 +112,7 @@ } - (NSString *)formattedDateString { if ([self useISOFormats]) { - return [NSString stringWithFormat:@"%d-%02d-%02d", + return [NSString stringWithFormat:@"%04d-%02d-%02d", [[self year] intValue], [[self month] intValue], [[self day] intValue]]; diff --git a/SOGo/UI/Scheduler/Version b/SOGo/UI/Scheduler/Version index 55934459..537a34c6 100644 --- a/SOGo/UI/Scheduler/Version +++ b/SOGo/UI/Scheduler/Version @@ -1,7 +1,8 @@ # $Id$ -SUBMINOR_VERSION:=111 +SUBMINOR_VERSION:=112 +# v0.9.112 requires SOGoLogic v0.9.12 # v0.9.107 requires WOExtensions v4.5.21 # v0.9.105 requires NGObjWeb v4.5.102 # v0.9.101 requires NGiCal v4.5.36 diff --git a/SOGo/UI/Scheduler/cycles.plist b/SOGo/UI/Scheduler/cycles.plist new file mode 100644 index 00000000..32589b95 --- /dev/null +++ b/SOGo/UI/Scheduler/cycles.plist @@ -0,0 +1,29 @@ +( + { + "label" = "cycle_once"; + }, + { + "label" = "cycle_daily"; + "rule" = "FREQ=DAILY"; + }, + { + "label" = "cycle_weekly"; + "rule" = "FREQ=WEEKLY"; + }, + { + "label" = "cycle_2weeks"; + "rule" = "FREQ=WEEKLY;INTERVAL=2"; + }, + { + "label" = "cycle_4weeks"; + "rule" = "FREQ=WEEKLY;INTERVAL=4"; + }, + { + "label" = "cycle_monthly"; + "rule" = "FREQ=MONTHLY"; + }, + { + "label" = "cycle_yearly"; + "rule" = "FREQ=YEARLY"; + } +) \ No newline at end of file diff --git a/SOGo/UI/Scheduler/product.plist b/SOGo/UI/Scheduler/product.plist index 62fd572e..5c2c7b62 100644 --- a/SOGo/UI/Scheduler/product.plist +++ b/SOGo/UI/Scheduler/product.plist @@ -21,6 +21,7 @@ skycalendar.js, green_corner.gif, invisible_space_2.gif, + cycles.plist, ); factories = { diff --git a/SOGoLogic/ChangeLog b/SOGoLogic/ChangeLog index 39f8d67e..cc20d83c 100644 --- a/SOGoLogic/ChangeLog +++ b/SOGoLogic/ChangeLog @@ -1,3 +1,12 @@ +2005-02-12 Marcus Mueller + + * v0.9.37 + + * SOGoAppointment.[hm]: added simplified API for getting/setting + recurrence rules + + * SOGoAppointmentICalRenderer.m: added rendering of recurrence rule + 2005-01-26 Marcus Mueller * AgenorUserManager.m: changed -getEmailForUID: to use diff --git a/SOGoLogic/SOGoAppointment.h b/SOGoLogic/SOGoAppointment.h index 774dd757..dfdd731f 100644 --- a/SOGoLogic/SOGoAppointment.h +++ b/SOGoLogic/SOGoAppointment.h @@ -33,8 +33,8 @@ OGoContentStore. */ -@class NSString, NSArray, NSCalendarDate; -@class iCalPerson, iCalEvent; +@class NSString, NSArray, NSCalendarDate, NGCalendarDateRange; +@class iCalPerson, iCalEvent, iCalRecurrenceRule; @interface SOGoAppointment : NSObject { @@ -103,6 +103,13 @@ /* attendees -> role == NON-PART */ - (NSArray *)resources; +/* simplified recurrence API */ +- (void)setRecurrenceRule:(iCalRecurrenceRule *)_rrule; +- (iCalRecurrenceRule *)recurrenceRule; +- (BOOL)hasRecurrenceRule; + +- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r; + /* iCal generation */ - (NSString *)iCalString; diff --git a/SOGoLogic/SOGoAppointment.m b/SOGoLogic/SOGoAppointment.m index 0d5e9f11..b0d5504d 100644 --- a/SOGoLogic/SOGoAppointment.m +++ b/SOGoLogic/SOGoAppointment.m @@ -43,6 +43,7 @@ static NGLogger *logger = nil; static BOOL didInit = NO; if (didInit) return; + didInit = YES; lm = [NGLoggerManager defaultLoggerManager]; logger = [lm loggerForClass:self]; @@ -389,6 +390,33 @@ static NGLogger *logger = nil; return nil; /* not found */ } + +/* + NOTE: this is not the same API as used by NGiCal! + SOGo/OGo cannot deal with the complete NGiCal API properly, although + SOGo COULD do so in the future +*/ +- (void)setRecurrenceRule:(iCalRecurrenceRule *)_rrule { + [_rrule retain]; + [self->event removeAllRecurrenceRules]; + if (_rrule) + [self->event addToRecurrenceRules:_rrule]; + [_rrule release]; +} +- (iCalRecurrenceRule *)recurrenceRule { + if ([self->event hasRecurrenceRules]) + return [[self->event recurrenceRules] objectAtIndex:0]; + return nil; +} +- (BOOL)hasRecurrenceRule { + return [self recurrenceRule] != nil; +} + +- (NSArray *)recurrenceRangesWithinCalendarDateRange:(NGCalendarDateRange *)_r { + return [self->event recurrenceRangesWithinCalendarDateRange:_r]; +} + + /* description */ - (void)appendAttributesToDescription:(NSMutableString *)_ms { diff --git a/SOGoLogic/SOGoAppointmentICalRenderer.m b/SOGoLogic/SOGoAppointmentICalRenderer.m index ecba50da..d0e53fdc 100644 --- a/SOGoLogic/SOGoAppointmentICalRenderer.m +++ b/SOGoLogic/SOGoAppointmentICalRenderer.m @@ -190,6 +190,13 @@ static unsigned DefaultICalStringCapacity = 1024; [s appendString:[_apt accessClass]]; [s appendString:@"\r\n"]; + /* recurrence rules */ + if ([_apt hasRecurrenceRule]) { + [s appendString:@"RRULE:"]; + [s appendString:[[_apt recurrenceRule] iCalRepresentation]]; + [s appendString:@"\r\n"]; + } + [self addOrganizer:[_apt organizer] toString:s]; [self addAttendees:[_apt attendees] toString:s]; diff --git a/SOGoLogic/Version b/SOGoLogic/Version index 338506ab..6fff7023 100644 --- a/SOGoLogic/Version +++ b/SOGoLogic/Version @@ -1,7 +1,8 @@ # Version file -SUBMINOR_VERSION:=36 +SUBMINOR_VERSION:=37 +# v0.9.37 requires NGiCal v4.5.39 # v0.9.34 requires libFoundation v1.0.67 # v0.9.32 requires NGiCal v4.5.37 # v0.9.31 requires NGiCal v4.5.36 -- 2.39.5