From 78045b14be6e343874b23743642c4a578a09a9d5 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Wed, 2 Nov 2016 18:16:45 -0400 Subject: [PATCH] (feat) first pass at support for recurring events/tasks email alarms (fixes #1053) --- NEWS | 3 + SOPE/GDLContentStore/GCSAlarmsFolder.h | 6 +- SOPE/GDLContentStore/GCSAlarmsFolder.m | 34 +-- SOPE/GDLContentStore/GCSFolder.m | 16 +- .../Appointments/SOGoAppointmentFolder.m | 6 +- .../Appointments/SOGoCalendarComponent.m | 26 --- .../Appointments/SOGoEMailAlarmsManager.h | 5 +- .../Appointments/SOGoEMailAlarmsManager.m | 194 +++++------------- SoObjects/Appointments/iCalCalendar+SOGo.m | 5 +- .../Appointments/iCalEntityObject+SOGo.h | 8 +- .../Appointments/iCalEntityObject+SOGo.m | 103 ++++++++-- SoObjects/Appointments/iCalEvent+SOGo.m | 3 +- SoObjects/Appointments/iCalToDo+SOGo.m | 3 +- SoObjects/Contacts/NGVCard+SOGo.m | 1 + SoObjects/Contacts/NGVList+SOGo.m | 4 +- Tools/SOGoEAlarmsNotifier.m | 36 +++- UI/Scheduler/UIxComponentEditor.m | 2 +- 17 files changed, 210 insertions(+), 245 deletions(-) diff --git a/NEWS b/NEWS index a411759f7..787120bb8 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ 3.2.2 (2016-11-DD) ------------------ +New features + - [core] support repetitive email alarms on tasks and events (#1053) + Bug fixes - [web] fixed mail settings persistence when sorting by arrival date diff --git a/SOPE/GDLContentStore/GCSAlarmsFolder.h b/SOPE/GDLContentStore/GCSAlarmsFolder.h index bd2c15cee..21d57b9ba 100644 --- a/SOPE/GDLContentStore/GCSAlarmsFolder.h +++ b/SOPE/GDLContentStore/GCSAlarmsFolder.h @@ -1,8 +1,6 @@ /* GCSAlarmsFolder.h - this file is part of $PROJECT_NAME_HERE$ * - * Copyright (C) 2010 Inverse inc. - * - * Author: Wolfgang Sourdeau + * Copyright (C) 2010-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -60,8 +58,6 @@ - (void) deleteRecordForEntryWithCName: (NSString *) cname inCalendarAtPath: (NSString *) path; -- (void) deleteRecordsForEntriesUntilDate: (NSCalendarDate *) date; - @end #endif /* GCSALARMSFOLDER_H */ diff --git a/SOPE/GDLContentStore/GCSAlarmsFolder.m b/SOPE/GDLContentStore/GCSAlarmsFolder.m index 911158bbd..6c05dd5d4 100644 --- a/SOPE/GDLContentStore/GCSAlarmsFolder.m +++ b/SOPE/GDLContentStore/GCSAlarmsFolder.m @@ -1,6 +1,6 @@ /* GCSAlarmsFolder.m - this file is part of SOGo * - * Copyright (C) 2010-2014 Inverse inc. + * Copyright (C) 2010-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -409,36 +409,4 @@ static NSString *alarmsFolderURLString = nil; } } -- (void) deleteRecordsForEntriesUntilDate: (NSCalendarDate *) date -{ - EOAdaptorChannel *tc; - EOAdaptorContext *context; - EOEntity *entity; - EOSQLQualifier *qualifier; - NSException *error; - - tc = [self _acquireStoreChannel]; - if (tc) - { - context = [tc adaptorContext]; - entity = [self _storeTableEntityForChannel: tc]; - qualifier = [[EOSQLQualifier alloc] initWithEntity: entity - qualifierFormat: - @"c_alarm_date <= %d", - (int) [date timeIntervalSince1970]]; - [qualifier autorelease]; - [context beginTransaction]; - error = [tc deleteRowsDescribedByQualifierX: qualifier]; - if (error) - { - [context rollbackTransaction]; - [self errorWithFormat:@"%s: cannot delete record: %@", - __PRETTY_FUNCTION__, error]; - } - else - [context commitTransaction]; - [self _releaseChannel: tc]; - } -} - @end diff --git a/SOPE/GDLContentStore/GCSFolder.m b/SOPE/GDLContentStore/GCSFolder.m index 5532e9b67..644d6351c 100644 --- a/SOPE/GDLContentStore/GCSFolder.m +++ b/SOPE/GDLContentStore/GCSFolder.m @@ -1017,7 +1017,21 @@ andAttribute: (EOAttribute *)_attribute || *_baseVersion == [storedVersion unsignedIntValue]) { /* extract quick info */ - quickRow = [theComponent performSelector: @selector(quickRecordFromContent:container:) withObject: _content withObject: theContainer]; + NSMethodSignature *aSignature; + NSInvocation *anInvocation; + SEL aSelector; + + aSelector = @selector(quickRecordFromContent:container:nameInContainer:); + aSignature = [[theComponent class] instanceMethodSignatureForSelector: aSelector]; + anInvocation = [NSInvocation invocationWithMethodSignature:aSignature]; + [anInvocation setSelector: aSelector]; + [anInvocation setTarget: theComponent]; + [anInvocation setArgument:&_content atIndex: 2]; + [anInvocation setArgument:&theContainer atIndex: 3]; + [anInvocation setArgument:&_name atIndex: 4]; + [anInvocation invoke]; + [anInvocation getReturnValue: &quickRow]; + if (quickRow) { [quickRow setObject:_name forKey:@"c_name"]; diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 24a23fb20..94c328df7 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -954,7 +954,7 @@ static Class iCalEventK = nil; // alarm defined. if ([[component alarms] count]) { - alarm = [component firstDisplayOrAudioAlarm]; + alarm = [component firstSupportedAlarm]; [row setObject: [NSNumber numberWithInt: [[alarm nextAlarmDate] timeIntervalSince1970]] forKey: @"c_nextalarm"]; } @@ -1030,7 +1030,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir (endDate && [dateRange containsDate: endDate])) { // We must pass nil to :container here in order to avoid re-entrancy issues. - newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]]; + newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil nameInContainer: nil]]; [ma replaceObjectAtIndex: recordIndex withObject: newRecord]; } else @@ -1047,7 +1047,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir // The recurrence id of the exception is outside the date range; // simply add the exception to the records array. // We must pass nil to :container here in order to avoid re-entrancy issues. - newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil]]; + newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil nameInContainer: nil]]; if ([newRecord objectForKey: @"startDate"] && [newRecord objectForKey: @"endDate"]) { newRecordRange = [NGCalendarDateRange calendarDateRangeWithStartDate: [newRecord objectForKey: @"startDate"] diff --git a/SoObjects/Appointments/SOGoCalendarComponent.m b/SoObjects/Appointments/SOGoCalendarComponent.m index 1c9c1ee36..4f05154da 100644 --- a/SoObjects/Appointments/SOGoCalendarComponent.m +++ b/SoObjects/Appointments/SOGoCalendarComponent.m @@ -683,15 +683,6 @@ newUid = [newUid substringToIndex: [newUid length]-4]; [newObject setUid: newUid]; } - - if ([[SOGoSystemDefaults sharedSystemDefaults] enableEMailAlarms]) - { - SOGoEMailAlarmsManager *eaMgr; - - eaMgr = [SOGoEMailAlarmsManager sharedEMailAlarmsManager]; - [eaMgr handleAlarmsInCalendar: [newObject parent] - fromComponent: self]; - } } - (NSException *) saveCalendar: (iCalCalendar *) newCalendar @@ -1459,23 +1450,6 @@ return nil; } -- (id) PUTAction: (WOContext *) localContext -{ - if ([[SOGoSystemDefaults sharedSystemDefaults] enableEMailAlarms]) - { - SOGoEMailAlarmsManager *eaMgr; - iCalCalendar *putCalendar; - WORequest *rq; - - rq = [localContext request]; - putCalendar = [iCalCalendar parseSingleFromSource: [rq contentAsString]]; - eaMgr = [SOGoEMailAlarmsManager sharedEMailAlarmsManager]; - [eaMgr handleAlarmsInCalendar: putCalendar - fromComponent: self]; - } - - return [super PUTAction: localContext]; -} // /* Overriding this method dramatically speeds up PROPFIND request, but may // otherwise be a bad idea... Wait and see. */ diff --git a/SoObjects/Appointments/SOGoEMailAlarmsManager.h b/SoObjects/Appointments/SOGoEMailAlarmsManager.h index d461361da..f6050a9ca 100644 --- a/SoObjects/Appointments/SOGoEMailAlarmsManager.h +++ b/SoObjects/Appointments/SOGoEMailAlarmsManager.h @@ -1,6 +1,6 @@ /* SOGoEMailAlarmsManager.h - this file is part of SOGo * - * Copyright (C) 2010-2014 Inverse inc. + * Copyright (C) 2010-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,8 +42,7 @@ /* fetch and cleanup */ - (NSArray *) scheduledAlarmsFromDate: (NSCalendarDate *) fromDate toDate: (NSCalendarDate *) toDate - withOwners: (NSMutableArray **) owners; -- (void) deleteAlarmsUntilDate: (NSCalendarDate *) untilDate; + withMetadata: (NSMutableArray *) metadata; @end diff --git a/SoObjects/Appointments/SOGoEMailAlarmsManager.m b/SoObjects/Appointments/SOGoEMailAlarmsManager.m index 2894bc22f..7a743f942 100644 --- a/SoObjects/Appointments/SOGoEMailAlarmsManager.m +++ b/SoObjects/Appointments/SOGoEMailAlarmsManager.m @@ -1,6 +1,6 @@ /* SOGoEMailAlarmsManager.m - this file is part of SOGo * - * Copyright (C) 2010-2014 Inverse inc. + * Copyright (C) 2010-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -35,87 +35,11 @@ #import #import "SOGoAppointmentFolder.h" +#import "SOGoAppointmentFolders.h" #import "SOGoCalendarComponent.h" #import "SOGoEMailAlarmsManager.h" -@interface iCalEntityObject (SOGoAlarmsExtension) - -- (void) findSoonerEMailAlarmAfterDate: (NSCalendarDate *) refDate - inAlarm: (iCalAlarm **) alarm; - -@end - -@interface iCalCalendar (SOGoAlarmsExtension) - -- (void) findSoonerEMailAlarmAfterDate: (NSCalendarDate *) refDate - inAlarm: (iCalAlarm **) alarm; - -@end - -@implementation iCalEntityObject (SOGoAlarmsExtension) - -- (void) findSoonerEMailAlarmAfterDate: (NSCalendarDate *) refDate - inAlarm: (iCalAlarm **) alarm -{ - int count, max; - NSArray *alarms; - iCalAlarm *currentAlarm; - NSString *action; - NSCalendarDate *soonerDate, *testDate; - - soonerDate = [*alarm nextAlarmDate]; - - alarms = [self alarms]; - max = [alarms count]; - for (count = 0; count < max; count++) - { - currentAlarm = [alarms objectAtIndex: count]; - action = [[currentAlarm action] uppercaseString]; - if ([action isEqualToString: @"EMAIL"]) - { - testDate = [currentAlarm nextAlarmDate]; - if (testDate - && [refDate earlierDate: testDate] == refDate - && (!soonerDate - || [soonerDate earlierDate: testDate] == testDate)) - { - *alarm = currentAlarm; - soonerDate = testDate; - } - } - } -} - -@end - -@implementation iCalCalendar (SOGoAlarmsExtension) - -- (void) findSoonerEMailAlarmAfterDate: (NSCalendarDate *) refDate - inAlarm: (iCalAlarm **) alarm -{ - int count, max; - iCalEntityObject *child; - BOOL done; - - /* Here we only search for email alarms in the first event. This - should be fixed sometime. */ - done = NO; - max = [children count]; - for (count = 0; !done && count < max; count++) - { - child = [children objectAtIndex: count]; - if ([child isKindOfClass: [iCalEvent class]] - || [child isKindOfClass: [iCalToDo class]]) - { - [child findSoonerEMailAlarmAfterDate: refDate inAlarm: alarm]; - done = YES; - } - } -} - -@end - @implementation SOGoEMailAlarmsManager + (id) sharedEMailAlarmsManager @@ -128,50 +52,11 @@ return sharedEMailAlarmsManager; } -- (void) _writeAlarmCoordinates: (iCalAlarm *) alarm - fromComponent: (SOGoCalendarComponent *) component -{ - GCSAlarmsFolder *af; - NSString *path, *cname; - iCalEntityObject *entity; - int alarmNbr; - - af = [[GCSFolderManager defaultFolderManager] alarmsFolder]; - - path = [[component container] ocsPath]; - cname = [component nameInContainer]; - if (alarm) - { - entity = [alarm parent]; - alarmNbr = [[entity alarms] indexOfObject: alarm]; - - [af writeRecordForEntryWithCName: cname - inCalendarAtPath: path - forUID: [entity uid] - recurrenceId: [entity recurrenceId] - alarmNumber: [NSNumber numberWithInt: alarmNbr] - andAlarmDate: [alarm nextAlarmDate]]; - } - else - [af deleteRecordForEntryWithCName: cname - inCalendarAtPath: path]; -} - -- (void) handleAlarmsInCalendar: (iCalCalendar *) calendar - fromComponent: (SOGoCalendarComponent *) component -{ - iCalAlarm *alarm; - NSCalendarDate *now; - - now = [NSCalendarDate calendarDate]; - - alarm = nil; - [calendar findSoonerEMailAlarmAfterDate: now - inAlarm: &alarm]; - [self _writeAlarmCoordinates: alarm - fromComponent: component]; -} - +// +// This method is called SOGoCalendarCompoent: -prepareDelete when +// a component is being deleted. We of course remove all associated +// when an event or task is being deleted. +// - (void) deleteAlarmsFromComponent: (SOGoCalendarComponent *) component { GCSAlarmsFolder *af; @@ -246,19 +131,52 @@ *owner = [parts objectAtIndex: 2]; } +- (void) _extractContainer: (NSString **) container + fromPath: (NSString *) path +{ + NSArray *parts; + + parts = [path componentsSeparatedByString: @"/"]; + if ([parts count] > 4) + *container = [parts objectAtIndex: 4]; +} + +- (SOGoAppointmentFolder *) _lookupContainerMatchingRecord: (NSDictionary *) record +{ + SOGoAppointmentFolders *folders; + NSString *container, *owner; + SOGoUserFolder *userFolder; + SOGoUser *user; + WOContext *context; + + [self _extractOwner: &owner fromPath: [record objectForKey: @"c_path"]]; + [self _extractContainer: &container fromPath: [record objectForKey: @"c_path"]]; + user = [SOGoUser userWithLogin: owner]; + userFolder = [SOGoUserFolder objectWithName: owner inContainer: nil]; + context = [WOContext context]; + [context setActiveUser: user]; + folders = [userFolder lookupName: @"Calendar" + inContext: context + acquire: NO]; + + return [folders lookupName: container + inContext: context + acquire: NO]; +} + - (iCalAlarm *) _lookupAlarmMatchingRecord: (NSDictionary *) record withOwner: (NSString **) owner + withEntity: (iCalEntityObject **) entity { iCalCalendar *calendar; - iCalEntityObject *entity; iCalAlarm *alarm; NSArray *alarms; int alarmNbr; calendar = [self _lookupCalendarMatchingRecord: record]; - entity = [self _lookupEntityMatchingRecord: record inCalendar: calendar]; + *entity = [self _lookupEntityMatchingRecord: record inCalendar: calendar]; alarmNbr = [[record objectForKey: @"c_alarm_number"] intValue]; - alarms = [entity alarms]; + alarms = [*entity alarms]; if (alarmNbr < [alarms count]) { alarm = [alarms objectAtIndex: alarmNbr]; @@ -276,8 +194,10 @@ - (NSArray *) scheduledAlarmsFromDate: (NSCalendarDate *) fromDate toDate: (NSCalendarDate *) toDate - withOwners: (NSMutableArray **) owners + withMetadata: (NSMutableArray *) metadata { + SOGoAppointmentFolder *container; + iCalEntityObject *entity; GCSAlarmsFolder *af; iCalAlarm *alarm; NSMutableArray *alarms; @@ -290,31 +210,25 @@ records = [af recordsForEntriesFromDate: fromDate toDate: toDate]; max = [records count]; alarms = [NSMutableArray arrayWithCapacity: max]; - if (owners) - *owners = [NSMutableArray arrayWithCapacity: max]; for (count = 0; count < max; count++) { record = [records objectAtIndex: count]; alarm = [self _lookupAlarmMatchingRecord: record - withOwner: &owner]; + withOwner: &owner + withEntity: &entity]; if (alarm) { + container = [self _lookupContainerMatchingRecord: record]; [alarms addObject: alarm]; - if (owners) - [*owners addObject: owner]; + [metadata addObject: [NSDictionary dictionaryWithObjectsAndKeys: owner, @"owner", + record, @"record", + container, @"container", + entity, @"entity", + nil]]; } } return alarms; } -- (void) deleteAlarmsUntilDate: (NSCalendarDate *) untilDate -{ - GCSAlarmsFolder *af; - - af = [[GCSFolderManager defaultFolderManager] alarmsFolder]; - - [af deleteRecordsForEntriesUntilDate: untilDate]; -} - @end diff --git a/SoObjects/Appointments/iCalCalendar+SOGo.m b/SoObjects/Appointments/iCalCalendar+SOGo.m index 5127ead44..ab3c78d9e 100644 --- a/SoObjects/Appointments/iCalCalendar+SOGo.m +++ b/SoObjects/Appointments/iCalCalendar+SOGo.m @@ -80,7 +80,8 @@ } - (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent - container: (id) theContainer + container: (id) theContainer + nameInContainer: (NSString *) nameInContainer { CardGroup *element; NSArray *elements; @@ -97,7 +98,7 @@ element = nil; } - return [(id)element quickRecordFromContent: theContent container: theContainer]; + return [(id)element quickRecordFromContent: theContent container: theContainer nameInContainer: nameInContainer]; } @end diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.h b/SoObjects/Appointments/iCalEntityObject+SOGo.h index 32b3a50dc..c90710422 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.h +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.h @@ -1,6 +1,6 @@ /* iCalEntityObject+SOGo.h - this file is part of SOGo * - * Copyright (C) 2007-2015 Inverse inc. + * Copyright (C) 2007-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -65,13 +65,15 @@ extern NSNumber *iCalDistantFutureNumber; forAllDay: (BOOL) allDay; - (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent - container: (id) theContainer; + container: (id) theContainer + nameInContainer: (NSString *) nameInContainer; - (iCalAlarm *) firstSupportedAlarm; - (iCalAlarm *) firstDisplayOrAudioAlarm; - (void) updateNextAlarmDateInRow: (NSMutableDictionary *) row - forContainer: (id) theContainer; + forContainer: (id) theContainer + nameInContainer: (NSString *) nameInContainer; @end diff --git a/SoObjects/Appointments/iCalEntityObject+SOGo.m b/SoObjects/Appointments/iCalEntityObject+SOGo.m index 55d5e1c7e..5db76046b 100644 --- a/SoObjects/Appointments/iCalEntityObject+SOGo.m +++ b/SoObjects/Appointments/iCalEntityObject+SOGo.m @@ -1,6 +1,6 @@ /* iCalEntityObject+SOGo.m - this file is part of SOGo * - * Copyright (C) 2007-2015 Inverse inc. + * Copyright (C) 2007-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +23,9 @@ #import #import +#import +#import + #import #import #import @@ -37,6 +40,7 @@ #import #import +#import #import #import #import @@ -554,6 +558,7 @@ NSNumber *iCalDistantFutureNumber = nil; - (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent container: (id) theContainer + nameInContainer: (NSString *) nameInContainer { [self subclassResponsibility: _cmd]; @@ -612,7 +617,6 @@ NSNumber *iCalDistantFutureNumber = nil; } return createdByData; -; } // @@ -659,10 +663,45 @@ NSNumber *iCalDistantFutureNumber = nil; return nil; } +- (iCalAlarm *) firstEmailAlarm +{ + iCalAlarm *anAlarm; + NSArray *alarms; + int i; + + alarms = [self alarms]; + + for (i = 0; i < [alarms count]; i++) + { + anAlarm = [[self alarms] objectAtIndex: i]; + + if ([[anAlarm action] caseInsensitiveCompare: @"EMAIL"] == NSOrderedSame) + return anAlarm; + } + + return nil; +} + + + - (void) updateNextAlarmDateInRow: (NSMutableDictionary *) row forContainer: (id) theContainer + nameInContainer: (NSString *) nameInContainer { NSCalendarDate *nextAlarmDate; + GCSAlarmsFolder *af; + NSString *path; + + if ([[SOGoSystemDefaults sharedSystemDefaults] enableEMailAlarms]) + { + af = [[GCSFolderManager defaultFolderManager] alarmsFolder]; + path = [theContainer ocsPath]; + } + else + { + af = nil; + path = nil; + } nextAlarmDate = nil; @@ -680,7 +719,7 @@ NSNumber *iCalDistantFutureNumber = nil; if (![(id)self isRecurrent]) { anAlarm = [self firstDisplayOrAudioAlarm]; - if (anAlarm) + if ((anAlarm = [self firstDisplayOrAudioAlarm])) { webstatus = [[anAlarm trigger] value: 0 ofAttribute: @"x-webstatus"]; if (!webstatus @@ -688,10 +727,27 @@ NSNumber *iCalDistantFutureNumber = nil; != NSOrderedSame)) nextAlarmDate = [anAlarm nextAlarmDate]; } - else - { - // TODO: handle email alarms here - } + else if ((anAlarm = [self firstEmailAlarm]) && af) + { + nextAlarmDate = [anAlarm nextAlarmDate]; + + // The email alarm is too old, let's just remove it + if ([nextAlarmDate earlierDate: [NSDate date]] == nextAlarmDate) + nextAlarmDate = nil; + else + { + int alarmNbr; + + alarmNbr = [[self alarms] indexOfObject: anAlarm]; + + [af writeRecordForEntryWithCName: nameInContainer + inCalendarAtPath: path + forUID: [self uid] + recurrenceId: nil + alarmNumber: [NSNumber numberWithInt: alarmNbr] + andAlarmDate: nextAlarmDate]; + } + } } // Recurring event/task else @@ -765,22 +821,30 @@ NSNumber *iCalDistantFutureNumber = nil; if ([[o alarms] count]) { - anAlarm = [self firstDisplayOrAudioAlarm]; - if (anAlarm) + if ((anAlarm = [self firstDisplayOrAudioAlarm])) { webstatus = [[anAlarm trigger] value: 0 ofAttribute: @"x-webstatus"]; if (!webstatus || ([webstatus caseInsensitiveCompare: @"TRIGGERED"] != NSOrderedSame)) - v = delta; nextAlarmDate = [NSDate dateWithTimeIntervalSince1970: [[[alarms objectAtIndex: i] objectForKey: @"c_nextalarm"] intValue]]; } - else - { - // TODO: handle email alarms here - } - } + else if ((anAlarm = [self firstEmailAlarm]) && af) + { + int alarmNbr; + + nextAlarmDate = [NSDate dateWithTimeIntervalSince1970: [[[alarms objectAtIndex: i] objectForKey: @"c_nextalarm"] intValue]]; + alarmNbr = [[self alarms] indexOfObject: anAlarm]; + + [af writeRecordForEntryWithCName: nameInContainer + inCalendarAtPath: path + forUID: [self uid] + recurrenceId: [self recurrenceId] + alarmNumber: [NSNumber numberWithInt: alarmNbr] + andAlarmDate: nextAlarmDate]; + } + } } } // for ( ... ) } // if (theContainer) @@ -791,7 +855,14 @@ NSNumber *iCalDistantFutureNumber = nil; [row setObject: [NSNumber numberWithInt: [nextAlarmDate timeIntervalSince1970]] forKey: @"c_nextalarm"]; else - [row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"]; + { + [row setObject: [NSNumber numberWithInt: 0] forKey: @"c_nextalarm"]; + + // Delete old email alarms + if (af) + [af deleteRecordForEntryWithCName: nameInContainer + inCalendarAtPath: [theContainer ocsPath]]; + } } @end diff --git a/SoObjects/Appointments/iCalEvent+SOGo.m b/SoObjects/Appointments/iCalEvent+SOGo.m index 0671d8f67..c5e134be5 100644 --- a/SoObjects/Appointments/iCalEvent+SOGo.m +++ b/SoObjects/Appointments/iCalEvent+SOGo.m @@ -68,6 +68,7 @@ // - (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent container: (id) theContainer + nameInContainer: (NSString *) nameInContainer { NSMutableDictionary *row; NSCalendarDate *startDate, *endDate; @@ -240,7 +241,7 @@ [partstates release]; /* handle alarms */ - [self updateNextAlarmDateInRow: row forContainer: theContainer]; + [self updateNextAlarmDateInRow: row forContainer: theContainer nameInContainer: nameInContainer]; /* handle categories */ categories = [self categories]; diff --git a/SoObjects/Appointments/iCalToDo+SOGo.m b/SoObjects/Appointments/iCalToDo+SOGo.m index 6ff4b6b06..6c7ae0e23 100644 --- a/SoObjects/Appointments/iCalToDo+SOGo.m +++ b/SoObjects/Appointments/iCalToDo+SOGo.m @@ -220,6 +220,7 @@ - (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent container: (id) theContainer + nameInContainer: (NSString *) nameInContainer { NSMutableDictionary *row; NSCalendarDate *startDate, *dueDate, *completed; @@ -350,7 +351,7 @@ [partstates release]; /* handle alarms */ - [self updateNextAlarmDateInRow: row forContainer: theContainer]; + [self updateNextAlarmDateInRow: row forContainer: theContainer nameInContainer: nameInContainer]; categories = [self categories]; if ([categories count] > 0) diff --git a/SoObjects/Contacts/NGVCard+SOGo.m b/SoObjects/Contacts/NGVCard+SOGo.m index d94b806aa..ec8c38bc9 100644 --- a/SoObjects/Contacts/NGVCard+SOGo.m +++ b/SoObjects/Contacts/NGVCard+SOGo.m @@ -898,6 +898,7 @@ convention: - (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent container: (id) theContainer + nameInContainer: (NSString *) nameInContainer { NSMutableDictionary *fields; CardElement *element; diff --git a/SoObjects/Contacts/NGVList+SOGo.m b/SoObjects/Contacts/NGVList+SOGo.m index e43393afa..8726e9e21 100644 --- a/SoObjects/Contacts/NGVList+SOGo.m +++ b/SoObjects/Contacts/NGVList+SOGo.m @@ -1,6 +1,6 @@ /* NGVCard+SOGo.m - this file is part of SOGo * - * Copyright (C) 2009-2014 Inverse inc. + * Copyright (C) 2009-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -69,6 +69,8 @@ - (NSMutableDictionary *) quickRecordFromContent: (NSString *) theContent container: (id) theContainer + nameInContainer: (NSString *) nameInContainer + { NSMutableDictionary *fields; NSString *value; diff --git a/Tools/SOGoEAlarmsNotifier.m b/Tools/SOGoEAlarmsNotifier.m index 0aefec114..f31da6f0f 100644 --- a/Tools/SOGoEAlarmsNotifier.m +++ b/Tools/SOGoEAlarmsNotifier.m @@ -1,6 +1,6 @@ /* SOGoEAlarmsNotifier.m - this file is part of SOGo * - * Copyright (C) 2011-2014 Inverse inc. + * Copyright (C) 2011-2016 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,12 +31,13 @@ #import -#import "SOGo/SOGoCredentialsFile.h" #import #import +#import #import #import #import +#import #import #import @@ -179,12 +180,15 @@ - (BOOL) run { - SOGoEMailAlarmsManager *eaMgr; - SOGoCredentialsFile *cf; NSCalendarDate *startDate, *toDate; - NSArray *alarms; - NSMutableArray *owners; + SOGoEMailAlarmsManager *eaMgr; + NSMutableArray *metadata; + iCalEntityObject *entity; + SOGoCredentialsFile *cf; NSString *credsFilename; + NSDictionary *d; + NSArray *alarms; + int count, max; if ([[NSUserDefaults standardUserDefaults] stringForKey: @"h"]) @@ -211,6 +215,7 @@ eaMgr = [NSClassFromString (@"SOGoEMailAlarmsManager") sharedEMailAlarmsManager]; + metadata = [[NSMutableArray alloc] init]; startDate = [NSCalendarDate calendarDate]; toDate = [startDate addYear: 0 month: 0 day: 0 hour: 0 minute: 0 @@ -219,13 +224,26 @@ hour: 0 minute: -5 second: 0] toDate: toDate - withOwners: &owners]; + withMetadata: metadata]; max = [alarms count]; for (count = 0; count < max; count++) [self _processAlarm: [alarms objectAtIndex: count] - withOwner: [owners objectAtIndex: count]]; + withOwner: [[metadata objectAtIndex: count] objectForKey: @"owner"]]; + + // We now update the next alarm date (if any, for recurring + // events or tasks for example). This will also delete any emai + // alarms that are no longer relevant + max = [metadata count]; + + for (count = 0; count < max; count++) + { + d = [metadata objectAtIndex: count]; + entity = [d objectForKey: @"entity"]; + [entity quickRecordFromContent: nil + container: [d objectForKey: @"container"] + nameInContainer: [[d objectForKey: @"record"] objectForKey: @"c_name"]]; + } - [eaMgr deleteAlarmsUntilDate: toDate]; return YES; } diff --git a/UI/Scheduler/UIxComponentEditor.m b/UI/Scheduler/UIxComponentEditor.m index b0f901a3d..872672682 100644 --- a/UI/Scheduler/UIxComponentEditor.m +++ b/UI/Scheduler/UIxComponentEditor.m @@ -552,7 +552,7 @@ static NSArray *reminderValues = nil; alarmData = nil; if ([component hasAlarms]) { - anAlarm = [component firstSupportedAlarm]; + anAlarm = [component firstDisplayOrAudioAlarm]; trigger = [anAlarm trigger]; if (![[trigger valueType] length] || [[trigger valueType] caseInsensitiveCompare: @"DURATION"] == NSOrderedSame) {