forked from robbiehanson/AlarmClock
-
Notifications
You must be signed in to change notification settings - Fork 0
/
AlarmScheduler.m
397 lines (319 loc) · 11.6 KB
/
AlarmScheduler.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#import "AlarmScheduler.h"
#import "Alarm.h"
#import "CalendarAdditions.h"
// Declare private methods
@interface AlarmScheduler (PrivateAPI)
+ (void)sortAndAddAlarm:(Alarm *)newAlarm;
@end
@implementation AlarmScheduler
// GLOBAL VARIABLES
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Alarm Array
static NSMutableArray *alarms;
// Storage of last alarm to sound
static Alarm *lastAlarm;
// INITIALIZATION, DEINITIALIZATION
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Initializes all the alarms and updates all the alarm times.
The alarms are initialized by by reading from app's prefs on disk.
The preferences are handled by Cocoa's NSUserDefaults system.
The preference file is stored at ~/Library/Preferences/com.digitallity.alarmclock2.plist
Note that this method is automatically called (courtesy of Cocoa) before the first method of this class is called.
However, it is directly called by the MenuController during the startup of the application.
This is because this class is in charge of storing all the alarms, and must be started immediately.
Since it's called directly, we use a static variable to prevent multiple calls to this method.
(One directly at startup, and the other indirectly via Cocoa the first time a method within it is called)
@result All alarms are initialized and ready to go.
**/
+ (void)initialize
{
static BOOL initialized = NO;
if(!initialized)
{
NSLog(@"Initializing AlarmScheduler...");
initialized = YES;
// Initialize alarms
NSArray *alarmsPrefs = [[NSUserDefaults standardUserDefaults] arrayForKey:@"Alarms"];
alarms = [[NSMutableArray alloc] initWithCapacity:[alarmsPrefs count]];
int i;
for(i=0; i<[alarmsPrefs count]; i++)
{
// Create alarm from dictionary
Alarm *temp = [[Alarm alloc] initWithDict:[alarmsPrefs objectAtIndex:i]];
// Update the alarm's time, and add into array if not expired
if([temp updateTime])
{
[self sortAndAddAlarm:temp];
}
// Release the alarm
// If it was added to the array, it will still be retained
[temp release];
}
// Save changes to defaults
[self savePrefs];
// Add listener for time zone change notifications
[[NSDistributedNotificationCenter defaultCenter] addObserver:self
selector:@selector(timeZoneDidChange:)
name:@"NSSystemTimeZoneDidChangeDistributedNotification"
object:nil];
// Note: The alarms individually update themselves during init if the time zone has changed
// since the last time the application was run.
}
}
/**
Called (via our application delegate) when the application is terminating.
All cleanup tasks should go here.
**/
+ (void)deinitialize
{
[[NSDistributedNotificationCenter defaultCenter] removeObserver:self];
}
// SAVING INFORMATION TO USER DEFAULTS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Loops through all the alarms, extracting their preferences (NSDictionary),
and then adds all alarms into an Array called Alarms, and adds this to the user defaults system.
**/
+ (void)savePrefs
{
NSMutableArray *alarmsPrefs = [NSMutableArray array];
int i;
for(i=0; i<[alarms count]; i++)
{
[alarmsPrefs addObject:[[alarms objectAtIndex:i] prefsDictionary]];
}
[[NSUserDefaults standardUserDefaults] setObject:alarmsPrefs forKey:@"Alarms"];
// Flush changes to disk
//[[NSUserDefaults standardUserDefaults] synchronize];
}
// GETTING ALARMS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Returns a reference to the alarm at the given index.
This reference is mainly to be used as a parameter for setAlarm.
It may be used for read only purposes, and should not be altered in any way.
@param index - Index of alarm in array.
**/
+ (Alarm *)alarmReferenceForIndex:(int)index
{
return [alarms objectAtIndex:index];
}
/**
Returns a clone (autoreleased copy) of the alarm at the given index.
This alarm is a completely separate copy, and may be freely altered.
This is the alarm object that should be edited when editing an alarm. (Not the alarm reference)
@param index - Index of alarm in vector.
**/
+ (Alarm *)alarmCloneForIndex:(int)index
{
return [[[alarms objectAtIndex:index] copy] autorelease];
}
// CHANGING ALARMS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Sets the alarm.
Since alarms may be changed, added, deleted or rescheduled, their position in an array is not constant.
For this reason a reference is used instead of an index.
The passed alarm is inserted directly into an array.
It is not copied, and therefore should not be altered after calling this method.
The reference in the array is replaced by the passed alarm (and thus released).
**/
+ (void)setAlarm:(Alarm *)clone forReference:(Alarm *)reference
{
// Remove the old alarm
[alarms removeObject:reference];
// Add the new alarm
[self sortAndAddAlarm:clone];
// Save changes to defaults
[self savePrefs];
// Post notification for changed alarm
[[NSNotificationCenter defaultCenter] postNotificationName:@"AlarmChanged" object:self];
}
/**
Adds the alarm to the list of alarms.
The passed alarm is inserted directly into an array.
It is not copied, and therefore should not be altered after calling this method.
**/
+ (void)addAlarm:(Alarm *)newAlarm
{
// Add the new alarm
[self sortAndAddAlarm:newAlarm];
// Save changes to defaults
[self savePrefs];
// Post notification for changed alarm
[[NSNotificationCenter defaultCenter] postNotificationName:@"AlarmChanged" object:self];
}
/**
Removes the given alarm from the list of alarms.
@param deletedAlarm - reference to alarm that is to be deleted.
**/
+ (void)removeAlarm:(Alarm *)deletedAlarm
{
// Remove alarm from array
[alarms removeObject:deletedAlarm];
// Save changes to defaults
[self savePrefs];
// Post notification for changed alarm
[[NSNotificationCenter defaultCenter] postNotificationName:@"AlarmChanged" object:self];
}
// UPDATING ALARMS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Loops through all the alarms in the list, and updates all of their times.
If the alarms are expired, they are removed from the list and deleted.
This method should be run when starting up the app, and after sleeping (if not set to wake the computer from sleep).
**/
+ (void)updateAllAlarms
{
int i;
for(i=0; i<[alarms count]; i++)
{
// Remove the alarm from the array
// We retain it first so it doesn't get released when removed
Alarm *temp = [[alarms objectAtIndex:i] retain];
[alarms removeObjectAtIndex:i];
// Now update the alarm, and add it back into the array if not expired
if([temp updateTime])
{
[self sortAndAddAlarm:temp];
}
// Release the alarm
// If it was added to the array, it will still be retained
[temp release];
}
// Save changes to defaults
[self savePrefs];
// Post notification for changed alarm
[[NSNotificationCenter defaultCenter] postNotificationName:@"AlarmChanged" object:self];
}
/**
This method is called when the time zone is changed.
It takes care of updating the times for all the alarms,
as well as storing the new time zone into the defaults system.
**/
+ (void)timeZoneDidChange:(NSNotification *)note
{
// Since the time zone has been changed, we need to first reset it
// This is because the time zone is set for our application when it starts (just like the language)
[NSTimeZone resetSystemTimeZone];
NSLog(@"Using Time Zone: %@", [[NSTimeZone systemTimeZone] name]);
// Loop through all the alarms, and update them to the new time zone
int i;
for(i = 0; i < [alarms count]; i++)
{
[[alarms objectAtIndex:i] updateTimeZone];
}
// Since the time has changed for all the alarms, we should go ahead and update the user defaults system
[self savePrefs];
// We might as well go ahead and update the menu too, just in case anything went wrong
// Post notification for changed alarm
[[NSNotificationCenter defaultCenter] postNotificationName:@"AlarmChanged" object:self];
}
// GETTING NUMBER OF ALARMS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* Returns the number of alarms that have been scheduled */
+ (int)numberOfAlarms
{
return [alarms count];
}
// GETTING INFO ABOUT NEXT AND LAST ALARM
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Returns a clone (autoreleased copy) of the last alarm to go off
**/
+ (Alarm *)lastAlarmClone
{
return [[lastAlarm copy] autorelease];
}
/**
Returns the date of the alarm that is scheduled to go off next.
The returned date is a clone (autoreleased copy).
If no alarm is scheduled, nil is returned
**/
+ (NSCalendarDate *)nextAlarmDate
{
int i;
for(i = 0; i < [alarms count]; i++)
{
if([[alarms objectAtIndex:i] isEnabled])
{
return [[[[alarms objectAtIndex:i] time] copy] autorelease];
}
}
return nil;
}
// QUERYING FOR SOUNDING ALARMS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Checks to see if any alarms are at the time they are scheduled to go off.
This method returns 3 possible responses.
It returns a negative value if there are no alarms ready to go off.
It returns zero is an alarm is ready to go off, but the alarm isn't enabled.
It returns a positive value if an alarm is ready to go off, and the alarm is enabled.
Note, that if this method returns a non-negative value (either zero or positive), it should continually be queried,
as there may be multiple alarms set for the same time.
**/
+ (int)alarmStatus:(NSCalendarDate *)now
{
if([alarms count] > 0)
{
Alarm *next = [alarms objectAtIndex:0];
//NSString *format = @"%Y-%m-%d %H:%M:%S %z";
//NSLog(@"Currt time: %@", [now descriptionWithCalendarFormat:format]);
//NSLog(@"Next alarm: %@", [[next time] descriptionWithCalendarFormat:format]);
if([[next time] timeIntervalSinceDate:now] <= 0.0)
{
// Set the lastAlarm
[lastAlarm autorelease];
lastAlarm = [next copy];
// Remove the alarm from the array
// First retain it so it doesn't get released when removed
[next retain];
[alarms removeObject:next];
// Update the alarm and resort in array if not expired
if([next updateTime])
{
[self sortAndAddAlarm:next];
}
// Release the alarm
// If it was added to the array, it will still be retained
[next release];
// Post notification for changed alarm
[[NSNotificationCenter defaultCenter] postNotificationName:@"AlarmChanged" object:self];
// Return YES if the alarm was enabled
if([lastAlarm isEnabled])
{
return 1;
}
else
{
return 0;
}
}
}
return -1;
}
// PRIVATE API
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/**
Adds the given alarm to the list of alarms and sorts it into the correct position.
Remember, we keep the alarms array sorted by alarm time.
**/
+ (void)sortAndAddAlarm:(Alarm *)newAlarm
{
// Put it in the right place
int i = 0;
BOOL done = false;
NSCalendarDate *newTime = [newAlarm time];
while(i<[alarms count] && !done)
{
Alarm *temp = [alarms objectAtIndex:i];
if([newTime isEarlierDate:[temp time]])
done = YES;
else
i++;
}
[alarms insertObject:newAlarm atIndex:i];
}
@end