[26] | 1 | //
|
---|
| 2 | // PSAlarms.m
|
---|
| 3 | // Pester
|
---|
| 4 | //
|
---|
| 5 | // Created by Nicholas Riley on Fri Oct 11 2002.
|
---|
| 6 | // Copyright (c) 2002 Nicholas Riley. All rights reserved.
|
---|
| 7 | //
|
---|
| 8 |
|
---|
| 9 | #import "PSAlarms.h"
|
---|
| 10 | #import "PSAlarm.h"
|
---|
[61] | 11 | #import "PSTimer.h"
|
---|
[53] | 12 | #import "NSDictionary-NJRExtensions.h"
|
---|
[26] | 13 |
|
---|
[53] | 14 | NSString * const PSAlarmImportException = @"PSAlarmImportException";
|
---|
| 15 |
|
---|
[26] | 16 | NSString * const PSAlarmsDidChangeNotification = @"PSAlarmsDidChangeNotification";
|
---|
[28] | 17 | NSString * const PSAlarmsNextAlarmDidChangeNotification = @"PSAlarmsNextAlarmDidChangeNotification";
|
---|
[26] | 18 |
|
---|
[53] | 19 | // NSUserDefaults key
|
---|
| 20 | static NSString * const PSPendingAlarms = @"Pester pending alarms"; // 1.0 Ð 1.1a3
|
---|
| 21 | static NSString * const PSAllAlarms = @"Pester alarms"; // 1.1a4 Ð
|
---|
[26] | 22 |
|
---|
[53] | 23 | // property list keys
|
---|
| 24 | static NSString * const PLAlarmsPending = @"pending";
|
---|
| 25 | static NSString * const PLAlarmsExpired = @"expired";
|
---|
| 26 |
|
---|
[26] | 27 | static PSAlarms *PSAlarmsAllAlarms = nil;
|
---|
| 28 |
|
---|
[28] | 29 | @interface PSAlarms (Private)
|
---|
| 30 |
|
---|
| 31 | - (void)_updateNextAlarm;
|
---|
| 32 |
|
---|
| 33 | @end
|
---|
| 34 |
|
---|
[26] | 35 | @implementation PSAlarms
|
---|
| 36 |
|
---|
[28] | 37 | + (void)setUp;
|
---|
[26] | 38 | {
|
---|
[64] | 39 | [PSTimer setUp];
|
---|
[26] | 40 | if (PSAlarmsAllAlarms == nil) {
|
---|
[53] | 41 | NSDictionary *plAlarms = [[NSUserDefaults standardUserDefaults] objectForKey: PSAllAlarms];
|
---|
| 42 | if (plAlarms == nil) {
|
---|
| 43 | PSAlarmsAllAlarms = [[self alloc] init];
|
---|
| 44 | } else {
|
---|
| 45 | PSAlarmsAllAlarms = [[self alloc] initWithPropertyList: plAlarms];
|
---|
| 46 | }
|
---|
[28] | 47 | [PSAlarmsAllAlarms _updateNextAlarm]; // only generate notifications after singleton established
|
---|
[26] | 48 | }
|
---|
| 49 | }
|
---|
| 50 |
|
---|
| 51 | + (PSAlarms *)allAlarms;
|
---|
| 52 | {
|
---|
[28] | 53 | NSAssert(PSAlarmsAllAlarms != nil, @"Attempt to use +[PSAlarms allAlarms] before setup complete");
|
---|
[26] | 54 | return PSAlarmsAllAlarms;
|
---|
| 55 | }
|
---|
| 56 |
|
---|
[53] | 57 | #pragma mark private
|
---|
| 58 |
|
---|
| 59 | - (void)_changed;
|
---|
| 60 | {
|
---|
| 61 | NSMutableArray *alarmsData = [[NSMutableArray alloc] init];
|
---|
| 62 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
---|
| 63 | [self _updateNextAlarm];
|
---|
| 64 | // NSLog(@"PSAlarms _changed:\n%@", alarms);
|
---|
| 65 | [defaults setObject: [self propertyListRepresentation] forKey: PSAllAlarms];
|
---|
| 66 | [defaults synchronize];
|
---|
| 67 | [alarmsData release];
|
---|
| 68 | [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsDidChangeNotification object: self];
|
---|
| 69 | }
|
---|
| 70 |
|
---|
| 71 | - (void)_alarmTimerExpired:(NSNotification *)notification;
|
---|
| 72 | {
|
---|
| 73 | PSAlarm *alarm = [notification object];
|
---|
[113] | 74 | NSLog(@"timer expired: %@ retainCount %d", alarm, [alarm retainCount]);
|
---|
[53] | 75 | [expiredAlarms addObject: alarm];
|
---|
[113] | 76 | NSLog(@"expired alarms: %@", [expiredAlarms description]);
|
---|
[53] | 77 | [alarms removeObject: alarm];
|
---|
| 78 | [self _changed];
|
---|
| 79 | }
|
---|
| 80 |
|
---|
| 81 | - (void)_alarmTimerSet:(NSNotification *)notification;
|
---|
| 82 | {
|
---|
| 83 | PSAlarm *alarm = [notification object];
|
---|
[113] | 84 | NSLog(@"timer set: %@ retainCount %d", alarm, [alarm retainCount]);
|
---|
[53] | 85 | [alarms addObject: alarm];
|
---|
| 86 | [expiredAlarms removeObject: alarm];
|
---|
| 87 | [self _changed];
|
---|
| 88 | }
|
---|
| 89 |
|
---|
| 90 | - (void)_alarmDied:(NSNotification *)notification;
|
---|
| 91 | {
|
---|
| 92 | PSAlarm *alarm = [notification object];
|
---|
[61] | 93 | // NSLog(@"alarm died: %@ retainCount %d", alarm, [alarm retainCount]);
|
---|
[53] | 94 | [alarms removeObject: alarm];
|
---|
| 95 | [expiredAlarms removeObject: alarm];
|
---|
| 96 | [self _changed];
|
---|
| 97 | }
|
---|
| 98 |
|
---|
[28] | 99 | - (void)_updateNextAlarm;
|
---|
| 100 | {
|
---|
| 101 | NSEnumerator *e;
|
---|
| 102 | PSAlarm *alarm, *oldNextAlarm = nextAlarm;
|
---|
| 103 | [nextAlarm release];
|
---|
| 104 | nextAlarm = nil;
|
---|
| 105 | // sort alarms so earliest is first
|
---|
[51] | 106 | [alarms sortUsingSelector: @selector(compareDate:)];
|
---|
[28] | 107 | // find first un-expired alarm
|
---|
| 108 | e = [alarms objectEnumerator];
|
---|
| 109 | while ( (alarm = [e nextObject]) != nil) {
|
---|
| 110 | if ([alarm isValid]) {
|
---|
| 111 | nextAlarm = [alarm retain];
|
---|
| 112 | break;
|
---|
| 113 | }
|
---|
| 114 | }
|
---|
| 115 | if (oldNextAlarm != nextAlarm)
|
---|
| 116 | [[NSNotificationCenter defaultCenter] postNotificationName: PSAlarmsNextAlarmDidChangeNotification object: nextAlarm];
|
---|
| 117 | }
|
---|
| 118 |
|
---|
[53] | 119 | - (void)_setUpNotifications;
|
---|
| 120 | {
|
---|
| 121 | [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerSet:) name: PSAlarmTimerSetNotification object: nil];
|
---|
| 122 | [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmTimerExpired:) name: PSAlarmTimerExpiredNotification object: nil];
|
---|
| 123 | [[NSNotificationCenter defaultCenter] addObserver: self selector: @selector(_alarmDied:) name: PSAlarmDiedNotification object: nil];
|
---|
| 124 | }
|
---|
| 125 |
|
---|
| 126 | #pragma mark initialize-release
|
---|
| 127 |
|
---|
[26] | 128 | - (id)init;
|
---|
| 129 | {
|
---|
| 130 | if ( (self = [super init]) != nil) {
|
---|
[28] | 131 | alarms = [[NSMutableArray alloc] init];
|
---|
[53] | 132 | expiredAlarms = [[NSMutableSet alloc] init];
|
---|
| 133 | [self _setUpNotifications];
|
---|
[26] | 134 | }
|
---|
| 135 | return self;
|
---|
| 136 | }
|
---|
| 137 |
|
---|
| 138 | - (void)dealloc;
|
---|
| 139 | {
|
---|
[28] | 140 | [alarms release];
|
---|
[53] | 141 | [expiredAlarms release];
|
---|
[26] | 142 | [[NSNotificationCenter defaultCenter] removeObserver: self];
|
---|
| 143 | [super dealloc];
|
---|
| 144 | }
|
---|
| 145 |
|
---|
[53] | 146 | #pragma mark accessing
|
---|
[26] | 147 |
|
---|
[53] | 148 | - (NSArray *)alarms;
|
---|
[26] | 149 | {
|
---|
[53] | 150 | return alarms;
|
---|
[26] | 151 | }
|
---|
| 152 |
|
---|
[28] | 153 | - (PSAlarm *)nextAlarm;
|
---|
| 154 | {
|
---|
| 155 | return nextAlarm;
|
---|
| 156 | }
|
---|
| 157 |
|
---|
[26] | 158 | - (int)alarmCount;
|
---|
| 159 | {
|
---|
| 160 | return [alarms count];
|
---|
| 161 | }
|
---|
| 162 |
|
---|
[602] | 163 | - (PSAlarm *)alarmAtIndex:(int)alarmIndex;
|
---|
[26] | 164 | {
|
---|
[602] | 165 | return [alarms objectAtIndex: alarmIndex];
|
---|
[26] | 166 | }
|
---|
| 167 |
|
---|
[602] | 168 | - (void)removeAlarmAtIndex:(int)alarmIndex;
|
---|
[26] | 169 | {
|
---|
[602] | 170 | [(PSAlarm *)[alarms objectAtIndex: alarmIndex] cancelTimer];
|
---|
| 171 | [alarms removeObjectAtIndex: alarmIndex];
|
---|
[26] | 172 | }
|
---|
| 173 |
|
---|
| 174 | - (void)removeAlarmsAtIndices:(NSArray *)indices;
|
---|
| 175 | {
|
---|
| 176 | NSEnumerator *e = [indices objectEnumerator];
|
---|
| 177 | NSNumber *n;
|
---|
[355] | 178 | unsigned indexCount = [indices count], i = 0, alarmIndex;
|
---|
| 179 | unsigned *indexArray = (unsigned *)malloc(indexCount * sizeof(unsigned));
|
---|
[364] | 180 | @try {
|
---|
[26] | 181 | while ( (n = [e nextObject]) != nil) {
|
---|
[28] | 182 | alarmIndex = [n intValue];
|
---|
[51] | 183 | [(PSAlarm *)[alarms objectAtIndex: alarmIndex] cancelTimer];
|
---|
[28] | 184 | indexArray[i] = alarmIndex;
|
---|
[26] | 185 | i++;
|
---|
| 186 | }
|
---|
| 187 | [alarms removeObjectsFromIndices: indexArray numIndices: indexCount];
|
---|
[364] | 188 | } @finally {
|
---|
[51] | 189 | free(indexArray); indexArray = NULL;
|
---|
[26] | 190 | [self _changed];
|
---|
[364] | 191 | }
|
---|
[26] | 192 | }
|
---|
| 193 |
|
---|
[51] | 194 | - (void)removeAlarms:(NSSet *)alarmsToRemove;
|
---|
| 195 | {
|
---|
| 196 | NSEnumerator *e = [alarms objectEnumerator];
|
---|
| 197 | PSAlarm *alarm;
|
---|
| 198 | NSMutableArray *indices = [NSMutableArray arrayWithCapacity: [alarmsToRemove count]];
|
---|
| 199 | int alarmIndex = 0;
|
---|
| 200 |
|
---|
| 201 | while ( (alarm = [e nextObject]) != nil) {
|
---|
| 202 | if ([alarmsToRemove containsObject: alarm])
|
---|
| 203 | [indices addObject: [NSNumber numberWithInt: alarmIndex]];
|
---|
| 204 | alarmIndex++;
|
---|
| 205 | }
|
---|
| 206 | [self removeAlarmsAtIndices: indices];
|
---|
| 207 | }
|
---|
| 208 |
|
---|
[113] | 209 | - (void)restoreAlarms:(NSSet *)alarmsToRestore;
|
---|
| 210 | {
|
---|
| 211 | [alarmsToRestore makeObjectsPerformSelector: @selector(resetTimer)];
|
---|
| 212 | }
|
---|
| 213 |
|
---|
[60] | 214 | - (BOOL)alarmsExpiring;
|
---|
| 215 | {
|
---|
| 216 | return [expiredAlarms count] != 0;
|
---|
| 217 | }
|
---|
| 218 |
|
---|
[105] | 219 | #pragma mark printing
|
---|
| 220 |
|
---|
| 221 | - (NSString *)description;
|
---|
| 222 | {
|
---|
| 223 | return [NSString stringWithFormat: @"%@ pending %@\n%@\n",
|
---|
| 224 | [super description], alarms,
|
---|
| 225 | [expiredAlarms count] > 0 ? [NSString stringWithFormat: @"expired %@\n", expiredAlarms]
|
---|
| 226 | : @""];
|
---|
| 227 | }
|
---|
| 228 |
|
---|
[53] | 229 | #pragma mark property list serialization (Pester 1.1)
|
---|
| 230 |
|
---|
| 231 | - (NSDictionary *)propertyListRepresentation;
|
---|
| 232 | {
|
---|
| 233 | NSMutableArray *plPendingAlarms = [[NSMutableArray alloc] initWithCapacity: [alarms count]];
|
---|
| 234 | NSMutableArray *plExpiredAlarms = [[NSMutableArray alloc] initWithCapacity: [expiredAlarms count]];
|
---|
| 235 | NSDictionary *plAllAlarms, *plAlarm;
|
---|
| 236 | NSEnumerator *e;
|
---|
| 237 | PSAlarm *alarm;
|
---|
| 238 |
|
---|
| 239 | e = [alarms objectEnumerator];
|
---|
| 240 | while ( (alarm = [e nextObject]) != nil) {
|
---|
| 241 | plAlarm = [alarm propertyListRepresentation];
|
---|
| 242 | if (plAlarm != nil)
|
---|
| 243 | [plPendingAlarms addObject: plAlarm];
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | e = [expiredAlarms objectEnumerator];
|
---|
| 247 | while ( (alarm = [e nextObject]) != nil) {
|
---|
| 248 | plAlarm = [alarm propertyListRepresentation];
|
---|
| 249 | if (plAlarm != nil)
|
---|
| 250 | [plExpiredAlarms addObject: plAlarm];
|
---|
| 251 | }
|
---|
| 252 |
|
---|
| 253 | plAllAlarms = [NSDictionary dictionaryWithObjectsAndKeys:
|
---|
| 254 | plPendingAlarms, PLAlarmsPending, plExpiredAlarms, PLAlarmsExpired, nil];
|
---|
| 255 | [plPendingAlarms release];
|
---|
| 256 | [plExpiredAlarms release];
|
---|
[105] | 257 |
|
---|
[53] | 258 | return plAllAlarms;
|
---|
| 259 | }
|
---|
| 260 |
|
---|
| 261 | - (id)initWithPropertyList:(NSDictionary *)dict;
|
---|
| 262 | {
|
---|
| 263 | if ( (self = [super init]) != nil) {
|
---|
| 264 | NSArray *plPendingAlarms = [dict objectForRequiredKey: PLAlarmsPending];
|
---|
| 265 | NSArray *plExpiredAlarms = [dict objectForRequiredKey: PLAlarmsExpired];
|
---|
| 266 | NSEnumerator *e;
|
---|
| 267 | NSDictionary *plAlarm;
|
---|
| 268 | PSAlarm *alarm;
|
---|
| 269 |
|
---|
| 270 | alarms = [[NSMutableArray alloc] initWithCapacity: [plPendingAlarms count]];
|
---|
| 271 | e = [plPendingAlarms objectEnumerator];
|
---|
| 272 | while ( (plAlarm = [e nextObject]) != nil) {
|
---|
[357] | 273 | alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm];
|
---|
| 274 | [alarms addObject: alarm];
|
---|
| 275 | [alarm release];
|
---|
[53] | 276 | }
|
---|
| 277 |
|
---|
| 278 | e = [plExpiredAlarms objectEnumerator];
|
---|
| 279 | while ( (plAlarm = [e nextObject]) != nil) {
|
---|
[113] | 280 | // expired alarms may be ready for deletion, or may repeat - if the latter, PSAlarm will reschedule the alarm so the repeat interval begins at restoration time.
|
---|
[357] | 281 | if ( (alarm = [[PSAlarm alloc] initWithPropertyList: plAlarm]) != nil) {
|
---|
[53] | 282 | [alarms addObject: alarm];
|
---|
[357] | 283 | [alarm release];
|
---|
| 284 | }
|
---|
[53] | 285 | }
|
---|
| 286 | expiredAlarms = [[NSMutableSet alloc] init];
|
---|
| 287 |
|
---|
| 288 | [self _setUpNotifications];
|
---|
| 289 | }
|
---|
| 290 | return self;
|
---|
| 291 | }
|
---|
| 292 |
|
---|
| 293 | #pragma mark archiving (Pester 1.0)
|
---|
| 294 |
|
---|
| 295 | - (unsigned)countOfVersion1Alarms;
|
---|
| 296 | {
|
---|
| 297 | return [[[NSUserDefaults standardUserDefaults] objectForKey: PSPendingAlarms] count];
|
---|
| 298 | }
|
---|
| 299 |
|
---|
| 300 | - (void)discardVersion1Alarms;
|
---|
| 301 | {
|
---|
| 302 | NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
|
---|
| 303 | [defaults removeObjectForKey: PSPendingAlarms];
|
---|
| 304 | [defaults synchronize];
|
---|
| 305 | }
|
---|
| 306 |
|
---|
| 307 | - (void)importVersion1Alarms;
|
---|
| 308 | {
|
---|
| 309 | NSArray *alarmsData = [[NSUserDefaults standardUserDefaults] arrayForKey: PSPendingAlarms];
|
---|
| 310 | NSEnumerator *e = [alarmsData objectEnumerator];
|
---|
| 311 | NSData *alarmData;
|
---|
| 312 | PSAlarm *alarm;
|
---|
[364] | 313 | NSMutableArray *importedAlarms = [[NSMutableArray alloc] initWithCapacity: [alarmsData count]];
|
---|
| 314 | @try {
|
---|
| 315 | while ( (alarmData = [e nextObject]) != nil) {
|
---|
| 316 | alarm = [NSUnarchiver unarchiveObjectWithData: alarmData];
|
---|
| 317 | if (alarm == nil)
|
---|
| 318 | @throw [NSException exceptionWithName: NSInternalInconsistencyException reason: @"Failed to decode Pester 1.0 alarm." userInfo: nil];
|
---|
| 319 | [importedAlarms addObject: alarm];
|
---|
| 320 | if (![alarm setTimer]) // expired
|
---|
| 321 | [alarms addObject: alarm];
|
---|
| 322 | }
|
---|
| 323 | } @catch (NSException *exception) {
|
---|
| 324 | [self removeAlarms: [NSSet setWithArray: importedAlarms]];
|
---|
| 325 | @throw;
|
---|
| 326 | } @finally {
|
---|
| 327 | [importedAlarms release];
|
---|
[53] | 328 | }
|
---|
| 329 | }
|
---|
| 330 |
|
---|
[26] | 331 | @end
|
---|