SoundNote/Classes/MGMController.m

368 lines
18 KiB
Objective-C

//
// MGMController.m
// SoundNote
//
// Created by Mr. Gecko on 7/4/10.
// Copyright (c) 2011 Mr. Gecko's Media (James Coleman). http://mrgeckosmedia.com/
//
// Permission to use, copy, modify, and/or distribute this software for any purpose
// with or without fee is hereby granted, provided that the above copyright notice
// and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT,
// OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
// DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
// ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
#import "MGMController.h"
#import "MGMFileManager.h"
#import "MGMLoginItems.h"
#import "MGMSound.h"
#import <GeckoReporter/GeckoReporter.h>
#import <Growl/GrowlApplicationBridge.h>
#import "MGMDisplayWatcher.h"
#import "MGMApplicationWatcher.h"
#import "MGMVolumeWatcher.h"
#import "MGMITunesWatcher.h"
#import "MGMUSBWatcher.h"
#import "MGMBluetoothWatcher.h"
#import "MGMNetworkWatcher.h"
#import "MGMPowerWatcher.h"
#import "MGMKeyboardWatcher.h"
#import "MGMMouseWatcher.h"
#import "MGMAccessibleWatcher.h"
#import "MGMTrashWatcher.h"
static NSAutoreleasePool *pool = nil;
void runloop(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
if (activity & kCFRunLoopEntry) {
if (pool!=nil) [pool drain];
pool = [NSAutoreleasePool new];
} else if (activity & kCFRunLoopExit) {
[pool drain];
pool = nil;
}
}
NSString * const MGMCopyright = @"Copyright (c) 2011 Mr. Gecko's Media (James Coleman). All rights reserved. http://mrgeckosmedia.com/";
NSString * const MGMVersion = @"MGMVersion";
NSString * const MGMLaunchCount = @"MGMLaunchCount";
NSString * const MGMApplicationSupportPath = @"~/Library/Application Support/MrGeckosMedia/SoundNote/";
NSString * const MGMNotesName = @"notes.rtf";
NSString * const MGMGrowlName = @"growl.plist";
NSString * const MGMDisabledName = @"disabled.plist";
NSString * const MGMSoundEndedNotification = @"MGMSoundEndedNotification";
NSString * const MGMNName = @"MGMNName";
NSString * const MGMNTitle = @"MGMNTitle";
NSString * const MGMNDescription = @"MGMNDescription";
NSString * const MGMNIcon = @"MGMNIcon";
NSString * const MGMNSound = @"MGMNSound";
NSString * const MGMNTask = @"MGMNTask";
@protocol NSFileManagerProtocol <NSObject>
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error;
- (BOOL)createDirectoryAtPath:(NSString *)path attributes:(NSDictionary *)attributes;
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error;
- (BOOL)removeFileAtPath:(NSString *)path handler:(id)handler;
- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (BOOL)copyPath:(NSString *)source toPath:(NSString *)destination handler:(id)handler;
- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (BOOL)movePath:(NSString *)source toPath:(NSString *)destination handler:(id)handler;
@end
static MGMController *MGMSharedController;
@implementation MGMController
+ (id)sharedController {
if (MGMSharedController==nil) {
MGMSharedController = [MGMController new];
}
return MGMSharedController;
}
- (id)init {
if (MGMSharedController!=nil) {
if ((self = [super init]))
[self release];
self = MGMSharedController;
} else if ((self = [super init])) {
MGMSharedController = self;
}
return self;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setup) name:MGMGRDoneNotification object:nil];
[MGMReporter sharedReporter];
}
- (void)setup {
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopEntry | kCFRunLoopExit, YES, 0, runloop, &context);
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
[GrowlApplicationBridge setGrowlDelegate:nil];
[[MGMLoginItems items] addSelf];
NSFileManager *manager = [NSFileManager defaultManager];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ([defaults objectForKey:MGMVersion]==nil || [[defaults objectForKey:MGMVersion] isEqual:@"0.2"]) {
if ([manager fileExistsAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath]]) {
[manager copyItemAtPath:[[NSBundle mainBundle] pathForResource:[MGMNotesName stringByDeletingPathExtension] ofType:[MGMNotesName pathExtension]] toPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMNotesName]];
[manager copyItemAtPath:[[NSBundle mainBundle] pathForResource:[MGMGrowlName stringByDeletingPathExtension] ofType:[MGMGrowlName pathExtension]] toPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMGrowlName]];
[manager copyItemAtPath:[[NSBundle mainBundle] pathForResource:[MGMDisabledName stringByDeletingPathExtension] ofType:[MGMDisabledName pathExtension]] toPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMDisabledName]];
[manager removeItemAtPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:@"note.txt"]];
[self showInstructions];
}
[defaults setObject:[[MGMSystemInfo info] applicationVersion] forKey:MGMVersion];
}
[self registerDefaults];
if (![manager fileExistsAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath]]) {
[manager createDirectoryAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath] withAttributes:nil];
[manager copyItemAtPath:[[NSBundle mainBundle] pathForResource:[MGMNotesName stringByDeletingPathExtension] ofType:[MGMNotesName pathExtension]] toPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMNotesName]];
[manager copyItemAtPath:[[NSBundle mainBundle] pathForResource:[MGMGrowlName stringByDeletingPathExtension] ofType:[MGMGrowlName pathExtension]] toPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMGrowlName]];
[manager copyItemAtPath:[[NSBundle mainBundle] pathForResource:[MGMDisabledName stringByDeletingPathExtension] ofType:[MGMDisabledName pathExtension]] toPath:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMDisabledName]];
[self showInstructions];
}
NSNotificationCenter *notificationCenter = [[NSWorkspace sharedWorkspace] notificationCenter];
[notificationCenter addObserver:self selector:@selector(willLogout:) name:NSWorkspaceWillPowerOffNotification object:nil];
[notificationCenter addObserver:self selector:@selector(willSleep:) name:NSWorkspaceWillSleepNotification object:nil];
[notificationCenter addObserver:self selector:@selector(didWake:) name:NSWorkspaceDidWakeNotification object:nil];
watchers = [NSMutableArray new];
notifications = [NSMutableArray new];
[self startNotificationWithInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"login", MGMNName, @"Login", MGMNTitle, @"You have logged in.", MGMNDescription, nil]];
NSDictionary *disabledWatchers = [NSDictionary dictionaryWithContentsOfFile:[[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMDisabledName]];
if (![[disabledWatchers objectForKey:@"display"] boolValue])
[self registerWatcher:[[MGMDisplayWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"application"] boolValue])
[self registerWatcher:[[MGMApplicationWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"volume"] boolValue])
[self registerWatcher:[[MGMVolumeWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"itunes"] boolValue])
[self registerWatcher:[[MGMITunesWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"usb"] boolValue])
[self registerWatcher:[[MGMUSBWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"bluetooth"] boolValue])
[self registerWatcher:[[MGMBluetoothWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"network"] boolValue])
[self registerWatcher:[[MGMNetworkWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"power"] boolValue])
[self registerWatcher:[[MGMPowerWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"keyboard"] boolValue])
[self registerWatcher:[[MGMKeyboardWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"mouse"] boolValue])
[self registerWatcher:[[MGMMouseWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"accessible"] boolValue])
[self registerWatcher:[[MGMAccessibleWatcher new] autorelease]];
if (![[disabledWatchers objectForKey:@"trash"] boolValue])
[self registerWatcher:[[MGMTrashWatcher new] autorelease]];
if ([defaults integerForKey:MGMLaunchCount]!=5) {
[defaults setInteger:[defaults integerForKey:MGMLaunchCount]+1 forKey:MGMLaunchCount];
if ([defaults integerForKey:MGMLaunchCount]==5) {
NSAlert *alert = [[NSAlert new] autorelease];
[alert setMessageText:@"Donations"];
[alert setInformativeText:@"Thank you for using SoundNote. SoundNote is donation supported software. If you like using it, please consider giving a donation to help with development."];
[alert addButtonWithTitle:@"Yes"];
[alert addButtonWithTitle:@"No"];
int result = [alert runModal];
if (result==1000)
[self donate:self];
}
}
}
- (void)dealloc {
[[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver:self];
[watchers release];
[notifications release];
[wakeTimer invalidate];
[wakeTimer release];
[lastUpdated release];
[growl release];
[super dealloc];
}
- (void)registerDefaults {
NSMutableDictionary *defaults = [NSMutableDictionary dictionary];
[defaults setObject:[NSNumber numberWithInt:1] forKey:MGMLaunchCount];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)theApplication hasVisibleWindows:(BOOL)flag {
[self showInstructions];
return YES;
}
- (IBAction)donate:(id)sender {
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=LMT7LSBTP4NDJ"]];
}
- (void)showInstructions {
if (instructions==nil) {
NSRect size = NSMakeRect(0, 0, 450, 400);
instructions = [[NSWindow alloc] initWithContentRect:size styleMask:NSTitledWindowMask | NSClosableWindowMask backing:NSBackingStoreBuffered defer:NO];
[instructions setTitle:@"SoundNote"];
NSScrollView *scrollview = [[[NSScrollView alloc] initWithFrame:size] autorelease];
[scrollview setHasVerticalScroller:YES];
[scrollview setHasHorizontalScroller:NO];
NSSize contentSize = [scrollview contentSize];
NSTextView *textView = [[[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, contentSize.width, contentSize.height)] autorelease];
[textView readRTFDFromFile:[[NSBundle mainBundle] pathForResource:@"Instructions" ofType:@"rtf"]];
[textView setEditable:NO];
[textView setVerticallyResizable:YES];
[textView setHorizontallyResizable:NO];
[textView setAutoresizingMask:NSViewHeightSizable];
[scrollview setDocumentView:textView];
[[textView textContainer] setHeightTracksTextView:YES];
[instructions setContentView:scrollview];
[instructions setDelegate:self];
[instructions setLevel:NSStatusWindowLevel];
[instructions setReleasedWhenClosed:YES];
[instructions center];
}
[instructions makeKeyAndOrderFront:self];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
}
- (void)windowWillClose:(NSNotification *)theNotification {
instructions = nil;
[[NSWorkspace sharedWorkspace] selectFile:[MGMApplicationSupportPath stringByExpandingTildeInPath] inFileViewerRootedAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath]];
}
- (void)registerWatcher:(id)theWatcher {
[watchers addObject:theWatcher];
}
- (NSMutableDictionary *)startNotificationWithInfo:(NSDictionary *)theInfo {
NSArray *allowedNotifications = [NSArray arrayWithObjects:@"logout", @"willsleep", @"didwake", nil];
if (ignoreNotifications && ![allowedNotifications containsObject:[theInfo objectForKey:MGMNName]])
return nil;
//NSLog(@"%@", theInfo);
NSArray *allowedExtensions = [NSArray arrayWithObjects:@"aiff", @"aif", @"mp3", @"wav", @"au", @"m4a", nil];
NSMutableDictionary *info = [[theInfo mutableCopy] autorelease];
NSFileManager *manager = [NSFileManager defaultManager];
NSArray *files = [manager contentsOfDirectoryAtPath:[MGMApplicationSupportPath stringByExpandingTildeInPath]];
for (int i=0; i<[files count]; i++) {
if ([[[files objectAtIndex:i] stringByDeletingPathExtension] isEqual:[info objectForKey:MGMNName]]) {
NSString *path = [[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:[files objectAtIndex:i]];
if ([allowedExtensions containsObject:[[path pathExtension] lowercaseString]]) {
MGMSound *sound = [[[MGMSound alloc] initWithContentsOfFile:path] autorelease];
[info setObject:sound forKey:MGMNSound];
[notifications addObject:info];
[sound setDelegate:self];
[sound play];
} else if ([[[path pathExtension] lowercaseString] isEqual:@"sh"]) {
NSTask *task = [[NSTask new] autorelease];
[task setLaunchPath:@"/bin/bash"];
NSMutableArray *arguments = [NSMutableArray arrayWithObject:path];
[arguments addObject:[info objectForKey:MGMNDescription]];
[task setArguments:arguments];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidTerminate:) name:NSTaskDidTerminateNotification object:task];
[info setObject:task forKey:MGMNTask];
[notifications addObject:info];
[task launch];
}
}
}
NSString *growlPath = [[MGMApplicationSupportPath stringByExpandingTildeInPath] stringByAppendingPathComponent:MGMGrowlName];
NSDictionary *attributes = [manager attributesOfItemAtPath:growlPath];
if (![[attributes objectForKey:NSFileModificationDate] isEqual:lastUpdated]) {
[growl release];
growl = [[NSDictionary dictionaryWithContentsOfFile:growlPath] retain];
[lastUpdated release];
lastUpdated = [[attributes objectForKey:NSFileModificationDate] retain];
}
if ([[growl objectForKey:[info objectForKey:MGMNName]] boolValue]) {
NSData *icon = nil;
if ([[info objectForKey:MGMNIcon] isKindOfClass:[NSData class]])
icon = [info objectForKey:MGMNIcon];
else if ([[info objectForKey:MGMNIcon] isKindOfClass:[NSImage class]])
icon = [[info objectForKey:MGMNIcon] TIFFRepresentation];
else
icon = [[[NSApplication sharedApplication] applicationIconImage] TIFFRepresentation];
[GrowlApplicationBridge notifyWithTitle:[info objectForKey:MGMNTitle] description:[info objectForKey:MGMNDescription] notificationName:[info objectForKey:MGMNName] iconData:icon priority:0 isSticky:NO clickContext:nil];
}
return info;
}
- (NSMutableDictionary *)notificationWithName:(NSString *)theName {
for (int i=0; i<[notifications count]; i++) {
if ([[[notifications objectAtIndex:i] objectForKey:MGMNName] isEqual:theName])
return [notifications objectAtIndex:i];
}
return nil;
}
- (void)soundDidFinishPlaying:(MGMSound *)theSound {
for (int i=0; i<[notifications count]; i++) {
if ([[notifications objectAtIndex:i] objectForKey:MGMNSound]==theSound) {
NSMutableDictionary *notification = [notifications objectAtIndex:i];
[notification removeObjectForKey:MGMNSound];
[[NSNotificationCenter defaultCenter] postNotificationName:MGMSoundEndedNotification object:notification];
if ([notification objectForKey:MGMNTask]==nil)
[notifications removeObject:notification];
}
}
}
- (void)taskDidTerminate:(NSNotification *)theNotification {
[[NSNotificationCenter defaultCenter] removeObserver:self name:[theNotification name] object:[theNotification object]];
for (int i=0; i<[notifications count]; i++) {
if ([[notifications objectAtIndex:i] objectForKey:MGMNTask]==[theNotification object]) {
NSMutableDictionary *notification = [notifications objectAtIndex:i];
[notification removeObjectForKey:MGMNTask];
if ([notification objectForKey:MGMNSound]==nil)
[notifications removeObject:notification];
}
}
}
- (void)willLogout:(NSNotification *)theNotification {
ignoreNotifications = YES;
[self startNotificationWithInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"logout", MGMNName, @"Logout", MGMNTitle, @"You are logging out.", MGMNDescription, nil]];
}
- (void)willSleep:(NSNotification *)theNotification {
if (wakeTimer!=nil) {
[wakeTimer invalidate];
[wakeTimer release];
wakeTimer = nil;
}
ignoreNotifications = YES;
NSMutableDictionary *info = [self startNotificationWithInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"willsleep", MGMNName, @"Will Sleep", MGMNTitle, @"The computer will go to sleep.", MGMNDescription, nil]];
if ([info objectForKey:MGMNSound]!=nil) {
while ([[info objectForKey:MGMNSound] isPlaying]) {
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
}
}
}
- (void)didWake:(NSNotification *)theNotification {
[self startNotificationWithInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"didwake", MGMNName, @"Did Wake", MGMNTitle, @"The computer woke up from sleep.", MGMNDescription, nil]];
wakeTimer = [[NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(sendWakeNotification) userInfo:nil repeats:NO] retain];
}
- (void)sendWakeNotification {
[wakeTimer release];
wakeTimer = nil;
ignoreNotifications = NO;
}
- (IBAction)Quit:(id)sender {
[[MGMLoginItems items] removeSelf];
[[NSApplication sharedApplication] terminate:self];
}
- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender {
NSMutableDictionary *info = [self notificationWithName:@"logout"];
if ([info objectForKey:MGMNSound]!=nil) {
while ([[info objectForKey:MGMNSound] isPlaying]) {
[NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.5]];
}
}
return NSTerminateNow;
}
@end