owntone-server/osx/Firefly Helper/FireflyHelper.m
2006-07-26 07:48:06 +00:00

623 lines
19 KiB
Objective-C
Raw Blame History

//
// FireflyHelper.m
//
// The "controller" part of our Model-View-Controller trio, plus what
// little "view" there is for this mostly-faceless program.
//
// The Firefly Helper manages setup and startup of the server, as well
// as communication withe the Prefs pane via Distributed Objects. It
// also optionally handles the Status Item (the menu by the clock in
// the menu bar).
//
// Created by Mike Kobb on 7/12/06.
// Copyright 2006 Roku, LLC. All rights reserved.
//
#import "FireflyHelper.h"
#import "FireflyServer.h"
#include "../FireflyCommon.h"
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/sysctl.h>
@implementation FireflyHelper
// ---------------------------------------------------------------------------
// init
//
// Note that we create this path in this way primarily because we cribbed
// the code from the prefs pane, and we eventually will probably
// change both, so for now it's best to be consistent.
// ---------------------------------------------------------------------------
- (id)init
{
protocolChecker = nil;
prefsConnection = nil;
fireflyServer = nil;
statusItem = nil;
client = nil;
clientIdent = 0;
return self;
}
// ---------------------------------------------------------------------------
// dealloc
// ---------------------------------------------------------------------------
- (void)dealloc
{
[protocolChecker release];
[prefsConnection release];
[statusItem release];
[FireflyServer release];
[super dealloc];
}
// ---------------------------------------------------------------------------
// applicationDidFinishLaunching
//
// We implement this delegate method so that we will be called when the
// Firefly Helper app is launched. We check to see if the prefs say
// we should launch the server when we launch, and if they do, we launch.
// ---------------------------------------------------------------------------
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Perform any checking of the installation here. Probably just look for
// the config file and put up a dialog if not found.
bool bSuccess = false;
NSString *errorString = @"";
do // while(false)
{
// Create and initialize our fireflyServer object
NSString *serverPath =
[[NSBundle bundleForClass:[self class]] pathForResource:@"firefly"
ofType:nil
inDirectory:@"Server"];
fireflyServer =
[[[FireflyServer alloc] initWithServerPath:serverPath] retain];
if( nil == fireflyServer )
{
errorString =
NSLocalizedString( @"Failed to initialize Firefly server",
@"explanatory text for failure to launch Firefly helper" );
break;
}
// Register for notifications from our server. Go ahead and do this before we
// start it.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(serverNotification:)
name:@STATUS_CHANGE
object:fireflyServer ];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(serverNotification:)
name:@VERSION_CHANGE
object:fireflyServer ];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(serverNotification:)
name:@URL_CHANGE
object:fireflyServer ];
// Must call this or Bonjour stuff won't work
[fireflyServer setup];
CFBooleanRef shouldLaunch = CFPreferencesCopyAppValue( CFSTR(FF_PREFS_LAUNCH_AT_LOGIN),
CFSTR(FF_PREFS_DOMAIN) );
if( NULL != shouldLaunch )
{
if( CFBooleanGetValue( shouldLaunch ) )
[fireflyServer start];
CFRelease( shouldLaunch );
}
// Okay, we're open for business. Let's vend our interface for
// the prefs pane to use
CFStringRef userName = CSCopyUserName( false );
NSString *serviceName = [@"FireflyHelper" stringByAppendingString:(NSString*)userName];
CFRelease(userName);
protocolChecker = [NSProtocolChecker
protocolCheckerWithTarget:self
protocol:@protocol(FireflyPrefsServerProtocol)];
prefsConnection = [NSConnection defaultConnection];
[prefsConnection setRootObject:protocolChecker];
if( ![prefsConnection registerName:serviceName] )
{
errorString =
NSLocalizedString( @"Unable to open communication channel for Preference pane",
@"explanatory text for failure to luanch Firefly helper" );
break;
}
// Made it through!
bSuccess = true;
// If we're supposed to put up a Menu Extra (NSStatusItem), do so
CFBooleanRef showMenu = CFPreferencesCopyAppValue( CFSTR(FF_PREFS_SHOW_MENU_EXTRA),
CFSTR(FF_PREFS_DOMAIN) );
if( NULL != showMenu )
{
if( CFBooleanGetValue( showMenu ) )
[self displayStatusItem];
CFRelease( showMenu );
}
} while( false );
// If we encountered a critical failure, we need to display an alert and
// then quit.
if( !bSuccess )
{
NSString *alertString = NSLocalizedString( @"Firefly cannot start",
@"Alert message when firefly helper can't start" );
NSString *quitString = NSLocalizedString( @"Quit",
@"Label for quit button in failure alert" );
NSAlert *alert = [NSAlert alertWithMessageText:alertString
defaultButton:quitString
alternateButton:nil
otherButton:nil
informativeTextWithFormat:errorString];
[alert setAlertStyle:NSCriticalAlertStyle];
[NSApp activateIgnoringOtherApps:YES];
[alert runModal];
[NSApp terminate:self];
}
}
// ---------------------------------------------------------------------------
// applicationWillTerminate
//
// We implement this delegate method so that we will be called when the
// Firefly Helper app is quitting. When the user logs out, we quit their
// Firefly server process.
// ---------------------------------------------------------------------------
- (void)applicationWillTerminate:(NSNotification *)aNotification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Notify the prefs pane we're bailing?
[fireflyServer shutdown]; // also stops the server
[prefsConnection invalidate];
}
// ---------------------------------------------------------------------------
// serverNotification
//
// Our handler for notifications from the server
// ---------------------------------------------------------------------------
- (void)serverNotification:(NSNotification *)theNotification
{
if( [[theNotification name] isEqualToString:@STATUS_CHANGE] )
{
[self tellClientStatusChanged:[fireflyServer status]];
}
else if( [[theNotification name] isEqualToString:@VERSION_CHANGE] )
{
[self tellClientVersionChanged:[fireflyServer version]];
}
else if( [[theNotification name] isEqualToString:@URL_CHANGE] )
{
[self tellClientURLChanged:[fireflyServer configURL]];
}
}
// ---------------------------------------------------------------------------
// checkClient
//
// Call this function to see if a client is really connected. If our client
// ivar is nil, returns NO, but if it's not nil, we try to "ping" the client.
// If that ping fails, the client ivar is released and set to nil, and we
// also return NO.
// ---------------------------------------------------------------------------
- (BOOL)checkClient
{
BOOL bRetVal = NO;
@try
{
bRetVal = [client stillThere];
}
@catch( NSException *exception )
{
[client autorelease];
client = nil;
NSLog( @"Client disappeared; setting to nil" );
}
return bRetVal;
}
// ---------------------------------------------------------------------------
// tellClientStatusChanged
//
// A wrapper for the call to statusChanged, that tests client for nil and
// traps any IPC-related exception. If an exception fires, this function
// will call our checkClient function to see if the client is still valid.
// ---------------------------------------------------------------------------
- (void)tellClientStatusChanged:(FireflyServerStatus)newStatus
{
if( nil != client )
{
@try
{
[client statusChanged:newStatus];
}
@catch( NSException *exception )
{
[self checkClient];
}
}
}
// ---------------------------------------------------------------------------
// tellClientVersionChanged
//
// A wrapper for the call to versionChanged. See tellClientStatusChanged
// for details.
// ---------------------------------------------------------------------------
- (void)tellClientVersionChanged:(NSString *)newVersion
{
if( nil != client )
{
@try
{
[client versionChanged:newVersion];
}
@catch( NSException *exception )
{
[self checkClient];
}
}
}
// ---------------------------------------------------------------------------
// tellClientURLChanged
//
// A wrapper for the call to configUrlChanged. See tellClientStatusChanged
// for details.
// ---------------------------------------------------------------------------
- (void)tellClientURLChanged:(NSString *)newURL
{
if( nil != client )
{
@try
{
[client configUrlChanged:newURL];
}
@catch( NSException * )
{
[self checkClient];
}
}
}
// ===========================================================================
// Items pertaining to the Status Item (our little menu bar item)
// ===========================================================================
// ---------------------------------------------------------------------------
// displayStatusItem
//
// Adds our Firefly menu to the menu bar
// ---------------------------------------------------------------------------
- (void)displayStatusItem
{
if( nil == statusItem )
{
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength:
NSSquareStatusItemLength] retain];
if( nil != statusItem )
{
[statusMenu setDelegate:self];
[statusItem setMenu:statusMenu];
NSImage *statusImage =
[[NSImage alloc] initWithContentsOfFile:
[[NSBundle mainBundle] pathForResource:@"ff_logo_status_menu"
ofType:@"tif"]];
[statusItem setImage:statusImage];
[statusItem setHighlightMode:YES];
}
}
}
// ---------------------------------------------------------------------------
// hideStatusItem
//
// Takes our Firefly menu out of the menu bar
// ---------------------------------------------------------------------------
- (void)hideStatusItem
{
if( nil != statusItem )
{
[[statusItem statusBar] removeStatusItem:statusItem];
[statusItem autorelease];
statusItem = nil;
}
}
// ---------------------------------------------------------------------------
// startStopMenuChosen
//
// Somebody chose "Start Firefly" or "Stop Firefly" from the menu
// ---------------------------------------------------------------------------
- (IBAction)startStopMenuChosen:(id)sender
{
if( nil != fireflyServer )
{
if( [fireflyServer isRunning] )
[fireflyServer stop];
else
[fireflyServer start];
}
}
// ---------------------------------------------------------------------------
// prefsMenuChosen
//
// Somebody chose "Firefly Preferences<65>"
// ---------------------------------------------------------------------------
- (IBAction)prefsMenuChosen:(id)sender
{
NSDictionary *errorDict = nil;
NSString *scriptSource = @"tell application \"System Preferences\"\n"
"activate\n"
"set current pane to pane \"org.fireflymediaserver.prefpanel\"\n"
"end tell\n";
NSAppleScript *myScript = [[NSAppleScript alloc] initWithSource:scriptSource];
if( nil != myScript )
{
[myScript executeAndReturnError:&errorDict];
[myScript release];
}
}
// ---------------------------------------------------------------------------
// numberOfItemsInMenu
//
// NSMenu delegate method. We always return -1 because we don't change the
// number of items in the menu.
// ---------------------------------------------------------------------------
- (int)numberOfItemsInMenu:(NSMenu *)menu
{
return -1;
}
// ---------------------------------------------------------------------------
// menuNeedsUpdate
//
// NSMenu delegate method. If our status has changed, we update the menu
// item text.
// ---------------------------------------------------------------------------
- (void)menuNeedsUpdate:(NSMenu *)menu
{
if( menu == statusMenu && 3 == [menu numberOfItems] )
{
// Just need to update the status of the server and the start/stop
// menu
id item = [menu itemAtIndex:0];
[item setTitle:StringForFireflyStatus( [self fireflyStatus] )];
item = [menu itemAtIndex:1];
if( [self fireflyIsRunning] )
{
[item setTitle:NSLocalizedString( @"Stop Firefly",
@"Text for status menu" )];
}
else
{
[item setTitle:NSLocalizedString( @"Start Firefly",
@"Text for status menu" )];
}
}
}
// ===========================================================================
// Implementation of FireflyPrefsProtocol
// ===========================================================================
// ---------------------------------------------------------------------------
// registerClient
//
// When the Prefs pane starts up and connects to us, it will register itself
// with us so that we may notify it of changes in the server status while
// it's open. This registration process also gives us the ability to detect
// when a
// ---------------------------------------------------------------------------
- (BOOL)registerClient:(id) newClient withIdentifier:(int) ident
{
BOOL bRetVal = NO;
if( nil != client )
{
// Hm. We already have a client connected. Let's see if it's really
// still there. This will set client to nil if the client has died.
if( [self checkClient] )
NSLog(@"registerClient called, but valid client already connected!\n");
}
if( nil == client )
{
client = [newClient retain];
clientIdent = ident;
bRetVal = YES;
}
return bRetVal;
}
// ---------------------------------------------------------------------------
// unregisterClientId
//
// When the Prefs pane is closing, it unregisters, so we know not to try to
// send it more udpates
// ---------------------------------------------------------------------------
- (void)unregisterClientId:(int) ident
{
if( ident == clientIdent )
{
clientIdent = 0;
[client autorelease];
client = nil;
}
}
// ---------------------------------------------------------------------------
// startFirefly
//
// Starts the server. Return value indicates that the server process was
// started. If a client has registered for status updates, it will see the
// status kFireflyStatusStarting. The server could possibly quit before
// coming online, so clients really should register for status updates.
// ---------------------------------------------------------------------------
- (FireflyStartResult)startFirefly
{
FireflyStartResult retVal = kFireflyStartFail;
if( nil != fireflyServer && [fireflyServer start] )
retVal = kFireflyStartSuccess;
return retVal;
}
// ---------------------------------------------------------------------------
// stopFirefly
//
// Signals the server to stop. A successful return value indicates that the
// server was not running, or has been successfully signaled to stop.
// Clients should register for status updates and look for the actual change
// to kFireflyStatusStopped to confirm shutdown.
// ---------------------------------------------------------------------------
- (FireflyStopResult)stopFirefly
{
FireflyStopResult retVal = kFireflyStopFail;
if( nil != fireflyServer && [fireflyServer stop] )
retVal = kFireflyStopSuccess;
return retVal;
}
// ---------------------------------------------------------------------------
// rescanLibrary
//
// Tells the server to re-scan the library. Returns a failure result if
// the server is not running.
// ---------------------------------------------------------------------------
- (FireflyRescanResult)rescanLibrary
{
FireflyRescanResult retVal = kFireflyRescanInvalid;
if( nil != fireflyServer )
retVal = [fireflyServer status];
return retVal;
}
// ---------------------------------------------------------------------------
// fireflyStatus
//
// Replies with the state of the server
// ---------------------------------------------------------------------------
- (FireflyServerStatus)fireflyStatus
{
FireflyServerStatus retVal = kFireflyStatusInvalid;
if( nil != fireflyServer )
retVal = [fireflyServer status];
return retVal;
}
// ---------------------------------------------------------------------------
// stopFirefly
//
// Signals the server to restart. A successful return value indicates that
// the server has been successfully signaled to restart.
// Clients should register for status updates and look for the actual changes
// in server status to verify that the signal is handled.
// ---------------------------------------------------------------------------
- (FireflyRestartResult)restartFirefly
{
FireflyRestartResult retVal = kFireflyRestartFail;
if( nil != fireflyServer && [fireflyServer restart] )
retVal = kFireflyRestartSuccess;
return retVal;
}
// ---------------------------------------------------------------------------
// fireflyVersion
//
// Replies with the version of the server. Returns nil if the
// server is not running or we are unable to ascertain the version. Note
// that a method invocation on nil returns nil, so we don't need to check
// fireflyServer for nil.
// ---------------------------------------------------------------------------
- (NSString *)fireflyVersion
{
return [fireflyServer version];
}
// ---------------------------------------------------------------------------
// fireflyConfigURL
//
// Replies with the URL to the advanced configuration page for the server.
// Returns nil if the server is not running or we are unable to
// ascertain the URL. See note about nil above.
// ---------------------------------------------------------------------------
- (NSString *)fireflyConfigURL
{
return [fireflyServer configURL];
}
// ---------------------------------------------------------------------------
// showHelperMenu
//
// Allows the prefs pane to specify whether to show the item. This setting
// is persistent
// ---------------------------------------------------------------------------
- (void)showHelperMenu:(BOOL)bShowMenu
{
if( bShowMenu )
[self displayStatusItem];
else
[self hideStatusItem];
}
// ---------------------------------------------------------------------------
// fireflyVersion
//
// Returns YES if the server is running, NO if not
// ---------------------------------------------------------------------------
- (BOOL)fireflyIsRunning
{
BOOL retVal = NO;
if( nil != fireflyServer )
retVal = [fireflyServer isRunning];
return retVal;
}
// ===========================================================================
// Implementation of NSMenuValidation
// ===========================================================================
// ---------------------------------------------------------------------------
// validateMenuItem
//
// Our first item is always disabled. Our last is always enabled. The
// one in the middle depends upon our status
// ---------------------------------------------------------------------------
- (BOOL)validateMenuItem:(id <NSMenuItem>)menuItem
{
BOOL bRetVal = NO;
if( nil != statusMenu && 3 == [statusMenu numberOfItems] )
{
if( menuItem == [statusMenu itemAtIndex:2] )
bRetVal = YES;
else if( menuItem == [statusMenu itemAtIndex:1] )
{
FireflyServerStatus status = [self fireflyStatus];
if( status != kFireflyStatusStopping && status != kFireflyStatusInvalid )
bRetVal = YES;
}
}
return bRetVal;
}
@end