mirror of
https://github.com/owntone/owntone-server.git
synced 2024-12-26 23:25:56 -05:00
623 lines
19 KiB
Objective-C
623 lines
19 KiB
Objective-C
//
|
||
// 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
|