2269 lines
73 KiB
Mathematica
2269 lines
73 KiB
Mathematica
|
// NOTE: The canonical Cocoa way to do things is with a strict Model-View-Controller
|
|||
|
// organization. However, that seems a bit silly for a simple case like a prefs
|
|||
|
// pane, so the OrgFireflyMediaServerPrefs object is both model and controller.
|
|||
|
|
|||
|
#import <Foundation/NSPathUtilities.h>
|
|||
|
#import <netinet/in.h>
|
|||
|
#include <errno.h>
|
|||
|
#include <stdlib.h>
|
|||
|
#include <stdio.h>
|
|||
|
#include <arpa/inet.h>
|
|||
|
#import "OrgFireflyMediaServerPrefs.h"
|
|||
|
#include "../FireflyCommon.h"
|
|||
|
|
|||
|
// Here we define some constants used when testing the existence of and accessing
|
|||
|
// the components of our installation.
|
|||
|
#define FIREFLY_HELPER_NAME "Firefly Helper.app"
|
|||
|
#define FIREFLY_HELPER_PROC_N "Firefly Helper"
|
|||
|
#define FIREFLY_PLUGIN_DIR "plugins"
|
|||
|
#define FIREFLY_LOG_FILE "firefly.log"
|
|||
|
#define FIREFLY_PLAYLIST_FILE "firefly.playlist"
|
|||
|
|
|||
|
@implementation OrgFireflyMediaServerPrefs
|
|||
|
|
|||
|
// ===========================================================================
|
|||
|
// Initialization and deallocation
|
|||
|
// ===========================================================================
|
|||
|
- (id)initWithBundle:(NSBundle *)bundle
|
|||
|
{
|
|||
|
if( ( self = [super initWithBundle:bundle] ) != nil )
|
|||
|
{
|
|||
|
appID = CFSTR( "org.fireflymediaserver.prefs" );
|
|||
|
|
|||
|
// Init our instance variables
|
|||
|
configFileStrings = [[NSMutableArray arrayWithCapacity:100] retain];
|
|||
|
configError = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
fireflyFolderPath = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
fireflyHelperPath = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
serverURL = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
logFilePath = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
playlistPath = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
userName = nil;
|
|||
|
configFilePath = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
serverName = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
serverPassword = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
libraryPath = [[NSMutableString stringWithCapacity:20] retain];
|
|||
|
serverProxy = nil;
|
|||
|
protocolChecker = nil;
|
|||
|
ipcTimer = nil;
|
|||
|
logTimer = nil;
|
|||
|
logDate = nil;
|
|||
|
srand((unsigned int)time(NULL));
|
|||
|
}
|
|||
|
|
|||
|
return self;
|
|||
|
}
|
|||
|
|
|||
|
- (void)dealloc
|
|||
|
{
|
|||
|
[configFileStrings release];
|
|||
|
[configError release];
|
|||
|
[fireflyFolderPath release];
|
|||
|
[fireflyHelperPath release];
|
|||
|
[serverURL release];
|
|||
|
[logFilePath release];
|
|||
|
[playlistPath release];
|
|||
|
[userName release];
|
|||
|
[configFilePath release];
|
|||
|
[serverName release];
|
|||
|
[serverPassword release];
|
|||
|
[libraryPath release];
|
|||
|
[serverProxy release];
|
|||
|
[protocolChecker release];
|
|||
|
[ipcTimer release];
|
|||
|
[logTimer release];
|
|||
|
[logDate release];
|
|||
|
[super dealloc];
|
|||
|
}
|
|||
|
|
|||
|
// ===========================================================================
|
|||
|
// NSPreferencePane methods for handling the installation and removal of
|
|||
|
// the panel. We use these to read our prefs, set up our UI, and start
|
|||
|
// and stop our scan for the server, as well as confirming whether a user
|
|||
|
// wants to apply changes.
|
|||
|
// ===========================================================================
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// willSelect
|
|||
|
//
|
|||
|
// NSPreferencePane instance method. We're about to be put on screen.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)willSelect
|
|||
|
{
|
|||
|
// NOTE: docs say default impl does nothing, so not necessary to call [super willSelect];
|
|||
|
|
|||
|
// Set up our user name (used for the library name as as IPC). Must do
|
|||
|
// this early, because setDefaultValues will need it to make the library
|
|||
|
// name. ("Copy" function name means no need to retain but we do need
|
|||
|
// to release later. CSStringRef is toll-free bridged to NSString*)
|
|||
|
[userName autorelease]; // in case we are being re-loaded within one Prefs session
|
|||
|
userName = (NSString*)CSCopyUserName( false );
|
|||
|
|
|||
|
// We're about to be loaded. Set up everything
|
|||
|
[self setDefaultValues];
|
|||
|
|
|||
|
// This is a bit of a hack. bConfigNeedsSaving will be set to YES upon
|
|||
|
// exit from validateInstall if validateInstall had to create a new
|
|||
|
// prefs file. We use this as a cue that it's a fresh install, and we
|
|||
|
// need to get the startup item installed (by calling saveSettings)
|
|||
|
bConfigNeedsSaving = NO;
|
|||
|
|
|||
|
if( ![self validateInstall] )
|
|||
|
{
|
|||
|
[self disableAllControls];
|
|||
|
bConfigNeedsSaving = NO;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if(![self loadSettings])
|
|||
|
{
|
|||
|
[configError setString:NSLocalizedString( @"Unable to read configuration information",
|
|||
|
@"Error message related to invalid config" ) ];
|
|||
|
configAppearsValid = NO;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If ValidateInstall told us it created a new file, then we are
|
|||
|
// going to do some hacky things. First, set bStartServerOnLogin to
|
|||
|
// false and save settings. This ensures that when we start firefly
|
|||
|
// Helper in a few seconds, it does not launch the server before the
|
|||
|
// user has a chance to set their settings. Then, we'll set it
|
|||
|
// back to its original value, and leave bConfigNeedsSaving
|
|||
|
// set. This way, when the user closes the panel or starts the server,
|
|||
|
// their changes will be set. Ugh.
|
|||
|
if( bConfigNeedsSaving )
|
|||
|
{
|
|||
|
BOOL priorVal = bStartServerOnLogin;
|
|||
|
bStartServerOnLogin = NO;
|
|||
|
[self saveSettings];
|
|||
|
CFPreferencesAppSynchronize( CFSTR(FF_PREFS_DOMAIN) ); // flush changes
|
|||
|
bConfigNeedsSaving = YES; // saveSettings sets to NO
|
|||
|
bStartServerOnLogin = priorVal;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Snag our current version
|
|||
|
NSString *versionString = [[NSBundle bundleForClass:[self class]]
|
|||
|
objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
|
|||
|
[panelVersionText setStringValue:versionString];
|
|||
|
|
|||
|
|
|||
|
if( configAppearsValid )
|
|||
|
{
|
|||
|
// GUI setup to initial state (note that although some of these are set in
|
|||
|
// the nib, we may be closed and then re-opened, so we need to set them
|
|||
|
// here.
|
|||
|
[browseButton setEnabled:YES];
|
|||
|
[nameField setEnabled:YES];
|
|||
|
[passwordCheckbox setEnabled:YES];
|
|||
|
[helperMenuCheckbox setEnabled:YES];
|
|||
|
[serverStartOptions setEnabled:YES];
|
|||
|
[mainTabView selectFirstTabViewItem:self];
|
|||
|
[nameField setStringValue:serverName];
|
|||
|
[libraryField setStringValue:libraryPath];
|
|||
|
[self setIconForPath];
|
|||
|
[passwordField setStringValue:serverPassword];
|
|||
|
if( [serverPassword length] > 0 )
|
|||
|
{
|
|||
|
[passwordCheckbox setState:NSOnState];
|
|||
|
[passwordField setEnabled:YES];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[passwordCheckbox setState:NSOffState];
|
|||
|
[passwordField setEnabled:NO];
|
|||
|
}
|
|||
|
[portField setIntValue:serverPort];
|
|||
|
if( 0 != serverPort )
|
|||
|
{
|
|||
|
[portField setEnabled:YES];
|
|||
|
[portPopup selectItemAtIndex:1];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[portField setEnabled:NO];
|
|||
|
[portPopup selectItemAtIndex:0];
|
|||
|
}
|
|||
|
if( bStartServerOnLogin )
|
|||
|
[serverStartOptions selectItemAtIndex:1];
|
|||
|
else
|
|||
|
[serverStartOptions selectItemAtIndex:0];
|
|||
|
|
|||
|
// bConfigNeedsSaving is configured above
|
|||
|
[applyNowButton setEnabled:bConfigNeedsSaving];
|
|||
|
|
|||
|
[helperMenuCheckbox setState:(bShowHelperMenu ? NSOnState : NSOffState)];
|
|||
|
|
|||
|
// Member setup to initial state (note, these are not our actual
|
|||
|
// preferences, which are set above). Rather, these are members for running
|
|||
|
// the prefs pane.
|
|||
|
serverProxy = nil;
|
|||
|
ipcTimer = nil;
|
|||
|
logTimer = nil;
|
|||
|
[logDate autorelease];
|
|||
|
logDate = [[NSDate distantPast] retain];
|
|||
|
|
|||
|
// Start by assuming that the server is not running.
|
|||
|
[self updateServerStatus:kFireflyStatusStopped];
|
|||
|
|
|||
|
// We always need the helper running when the panel is running,
|
|||
|
// so launch it if it's not already running
|
|||
|
[self launchHelperIfNeeded];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// didSelect
|
|||
|
//
|
|||
|
// NSPreferencePane instance method. We're now on screen.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)didSelect
|
|||
|
{
|
|||
|
// NOTE: docs say default impl does nothing, so not necessary to call [super didSelect];
|
|||
|
|
|||
|
// We've been loaded and are on screen.
|
|||
|
|
|||
|
// Did we encounter any errors at startup that will prevent us from doing work? If so,
|
|||
|
// here's where we put up a sheet to explain.
|
|||
|
if( configAppearsValid )
|
|||
|
{
|
|||
|
// No errors. We could go ahead and try right now to establish
|
|||
|
// Connection. BUT, since we may be being opened in response to the
|
|||
|
// Helper application's menu choice, we avoid a possible (temporary)
|
|||
|
// deadlock by doing our first proxy attempt in the timer function,
|
|||
|
// which allows didSelect to return and let the Apple Event complete.
|
|||
|
#if 0
|
|||
|
//
|
|||
|
if( [self makeProxyConnection] )
|
|||
|
{
|
|||
|
[self updateServerStatus:[self fireflyStatus]];
|
|||
|
NSString *string = [self fireflyVersion];
|
|||
|
if( nil != string )
|
|||
|
[self versionChanged:string];
|
|||
|
string = [self fireflyConfigURL];
|
|||
|
if( nil != string )
|
|||
|
[self configUrlChanged:string];
|
|||
|
}
|
|||
|
else
|
|||
|
#endif
|
|||
|
{
|
|||
|
[startStopButton setEnabled:NO];
|
|||
|
[statusText setStringValue:NSLocalizedString( @"Checking Firefly status<75>",
|
|||
|
@"Status text for when Firefly state is not known" )];
|
|||
|
[progressSpinner startAnimation:self];
|
|||
|
ipcTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0
|
|||
|
target:self
|
|||
|
selector:@selector(proxyTimerFired:)
|
|||
|
userInfo:nil
|
|||
|
repeats:YES] retain];
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
NSString *errorIntro = NSLocalizedString( @"Firefly appears to be incorrectly installed or damaged. "
|
|||
|
"Please consult the documentation.\n\n",
|
|||
|
@"Explanatory text for the failure-to-apply alert" );
|
|||
|
NSString *errorString = [errorIntro stringByAppendingString:configError];
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Configuration error",
|
|||
|
@"Alert message notifying the user of config error" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
errorString );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// shouldUnselect
|
|||
|
//
|
|||
|
// NSPreferencePane delegate method
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (NSPreferencePaneUnselectReply)shouldUnselect
|
|||
|
{
|
|||
|
// We write our config when the user clicks "Apply Now". If they've made changes
|
|||
|
// but not clicked the button, we need to ask them here if they want to save.
|
|||
|
|
|||
|
// if changes need saving, we want to put up a sheet asking if they want to apply
|
|||
|
// NOTE: Sheets are complicated to deal with, because you have to handle their results
|
|||
|
// in delegate methods, and it gets a bit wonky if handling the result in turn
|
|||
|
// requires another modal dialog. Anyway, we post the sheet here. Look for sheetDidEnd to
|
|||
|
// see the handling of the result.
|
|||
|
if( bConfigNeedsSaving )
|
|||
|
{
|
|||
|
// Even more complicated than the average sheet (where we could call NSBeginAlertSheet),
|
|||
|
// because we offer Cmd-D for "Don't apply"
|
|||
|
NSAlert *alert = [[NSAlert alloc] init];
|
|||
|
[alert setMessageText:NSLocalizedString( @"Apply configuration changes?",
|
|||
|
@"Prompt to save changes when exiting prefs pane" )];
|
|||
|
[alert addButtonWithTitle:NSLocalizedString( @"Apply", @"Label for apply button in save prompt dialog" )];
|
|||
|
[alert addButtonWithTitle:NSLocalizedString( @"Cancel", @"Label for cancel button in save prompt dialog" )];
|
|||
|
NSButton *button;
|
|||
|
button = [alert addButtonWithTitle:NSLocalizedString( @"Don't Apply",
|
|||
|
@"Label for dont' apply button in save prompt dialog" )];
|
|||
|
[button setKeyEquivalent:@"d"];
|
|||
|
[button setKeyEquivalentModifierMask:NSCommandKeyMask];
|
|||
|
[alert beginSheetModalForWindow:[[self mainView] window]
|
|||
|
modalDelegate:self
|
|||
|
didEndSelector:@selector(applySheetDidEnd:returnCode:contextInfo:)
|
|||
|
contextInfo:nil];
|
|||
|
return NSUnselectLater;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
return NSUnselectNow;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// willUnselect
|
|||
|
//
|
|||
|
// NSPreferencePane delegate method
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)willUnselect
|
|||
|
{
|
|||
|
// NOTE: docs say default impl does nothing, so not necessary to call
|
|||
|
// [super willUnselect];
|
|||
|
|
|||
|
// We could be unselected, then reselected, so there are a few objects
|
|||
|
// where we need to go ahead and disconnect them and then release
|
|||
|
// them so we can re-create if we're reloaded.
|
|||
|
[ipcTimer invalidate];
|
|||
|
[ipcTimer autorelease];
|
|||
|
ipcTimer = nil;
|
|||
|
[logTimer invalidate];
|
|||
|
[logTimer autorelease];
|
|||
|
logTimer = nil;
|
|||
|
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
[serverProxy unregisterClientId:clientIdent];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"willUnselect caught %@: %@",
|
|||
|
[exception name], [exception reason]);
|
|||
|
}
|
|||
|
@finally
|
|||
|
{
|
|||
|
[serverProxy autorelease];
|
|||
|
serverProxy = nil;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Flush our prefs
|
|||
|
CFPreferencesAppSynchronize( CFSTR(FF_PREFS_DOMAIN) );
|
|||
|
|
|||
|
// Last, make sure the login item is set up appropriately, no matter how
|
|||
|
// we are getting out of here.
|
|||
|
[self updateLoginItem];
|
|||
|
}
|
|||
|
|
|||
|
// ===========================================================================
|
|||
|
// Functions to handle user input and interaction
|
|||
|
// ===========================================================================
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// browseButtonClicked:
|
|||
|
//
|
|||
|
// User wants to change the library location. Pop up an "Open" sheet
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)browseButtonClicked:(id)sender
|
|||
|
{
|
|||
|
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
|||
|
[panel setCanChooseDirectories:YES];
|
|||
|
[panel setCanChooseFiles:NO];
|
|||
|
[panel setResolvesAliases:YES];
|
|||
|
[panel setPrompt:NSLocalizedString( @"Choose", @"The Choose button in the library browser dialog" )];
|
|||
|
[panel setTitle:NSLocalizedString( @"Choose Library Location", @"Title of the library browser dialog" )];
|
|||
|
[panel setMessage:NSLocalizedString(
|
|||
|
@"Please select the folder containing your music library, then click Choose.",
|
|||
|
@"Info text for the library browse dialog" )];
|
|||
|
NSString *path = [@"~/" stringByExpandingTildeInPath]; // default
|
|||
|
NSString *file = nil;
|
|||
|
NSFileManager *mgr = [NSFileManager defaultManager];
|
|||
|
BOOL bIsDir = NO;
|
|||
|
if( [mgr fileExistsAtPath:libraryPath isDirectory:&bIsDir] && bIsDir )
|
|||
|
{
|
|||
|
file = [libraryPath lastPathComponent];
|
|||
|
path = [libraryPath stringByDeletingLastPathComponent];
|
|||
|
}
|
|||
|
|
|||
|
[panel beginSheetForDirectory:path
|
|||
|
file:file
|
|||
|
types:nil
|
|||
|
modalForWindow:[[self mainView] window]
|
|||
|
modalDelegate:self
|
|||
|
didEndSelector:@selector(browsePanelEnded:returnCode:contextInfo:)
|
|||
|
contextInfo:nil];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// browsePanelEnded:returnCode:contextInfo:
|
|||
|
//
|
|||
|
// Delegate method for the "Open" sheet. Handle the user's choice.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)browsePanelEnded:(NSOpenPanel *)panel returnCode:(int)panelResult contextInfo:(void *)contextInfo
|
|||
|
{
|
|||
|
if( NSOKButton == panelResult )
|
|||
|
{
|
|||
|
NSArray *selectedDirArray = [panel filenames];
|
|||
|
if( 0 < [selectedDirArray count] )
|
|||
|
{
|
|||
|
[libraryPath setString:[selectedDirArray objectAtIndex:0]];
|
|||
|
[libraryField setStringValue:libraryPath];
|
|||
|
[self setIconForPath];
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// passwordChanged:
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)passwordChanged:(id)sender
|
|||
|
{
|
|||
|
if( NSOrderedSame != [serverPassword compare:[passwordField stringValue]] )
|
|||
|
{
|
|||
|
[serverPassword setString:[passwordField stringValue]];
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// shareNameChanged:
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)shareNameChanged:(id)sender
|
|||
|
{
|
|||
|
if( NSOrderedSame != [serverName compare:[nameField stringValue]] )
|
|||
|
{
|
|||
|
[serverName setString:[nameField stringValue]];
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// portPopupChanged:
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)portPopupChanged:(id)sender
|
|||
|
{
|
|||
|
if( 0 == [portPopup indexOfSelectedItem] )
|
|||
|
{
|
|||
|
#if 0
|
|||
|
[portField abortEditing];
|
|||
|
[portField setIntValue:currentServerPort];
|
|||
|
[portField setEnabled:false];
|
|||
|
#endif
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[portField setEnabled:true];
|
|||
|
[[[self mainView] window] makeFirstResponder:portField];
|
|||
|
}
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// portChanged:
|
|||
|
//
|
|||
|
// The value of the port changed
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)portChanged:(id)sender
|
|||
|
{
|
|||
|
if( serverPort != [portField intValue] )
|
|||
|
{
|
|||
|
serverPort = [portField intValue];
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// pwCheckBoxChanged:
|
|||
|
//
|
|||
|
// User changed the state of the "Require Password" checkbox.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)pwCheckBoxChanged:(id)sender
|
|||
|
{
|
|||
|
if( NSOffState == [passwordCheckbox state] )
|
|||
|
{
|
|||
|
[passwordField validateEditing];
|
|||
|
[passwordField setStringValue:@""];
|
|||
|
[serverPassword setString:@""];
|
|||
|
[passwordField setEnabled:false];
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[passwordField setEnabled:true];
|
|||
|
[[[self mainView] window] makeFirstResponder:passwordField];
|
|||
|
if( 0 < [serverPassword length] )
|
|||
|
[self setConfigNeedsSaving:YES]; // Only enable if there's a password
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// serverStartOptionChanged:
|
|||
|
//
|
|||
|
// User changed the popup menu of server options.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)serverStartOptionChanged:(id)sender
|
|||
|
{
|
|||
|
bStartServerOnLogin = ( 1 == [serverStartOptions indexOfSelectedItem] );
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// startStopButtonClicked:
|
|||
|
//
|
|||
|
// Start or stop the server.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)startStopButtonClicked:(id)sender
|
|||
|
{
|
|||
|
if( ![self fireflyIsRunning] )
|
|||
|
{
|
|||
|
// Server is not running, so we need to start it. First, let's see
|
|||
|
// if we have unsaved changes
|
|||
|
BOOL bOKToStart = !bConfigNeedsSaving;
|
|||
|
if( bConfigNeedsSaving &&
|
|||
|
[[[self mainView] window] makeFirstResponder:[[self mainView] window]] &&
|
|||
|
[self currentTabIsValid] )
|
|||
|
{
|
|||
|
if( [self saveSettings] )
|
|||
|
{
|
|||
|
[applyNowButton setEnabled:NO];
|
|||
|
bOKToStart = YES;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Failed to save changes",
|
|||
|
@"Alert message notifying the user of failure to save" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NSLocalizedString( @"Firefly could not be started because your changes "
|
|||
|
"could not be saved",
|
|||
|
@"Explanatory text for the failure-to-save alert" ) );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if( bOKToStart )
|
|||
|
{
|
|||
|
[self updateServerStatus:kFireflyStatusStarting];
|
|||
|
if( ![self startFirefly] )
|
|||
|
{
|
|||
|
[self updateServerStatus:kFireflyStatusInvalid];
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Unable to start Firefly",
|
|||
|
@"Alert message notifying the user of failure to stop" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NSLocalizedString( @"An unexpected error occurred when trying to start Firefly. "
|
|||
|
"Please close and re-open this Preference pane, and try again.",
|
|||
|
@"Explanatory text for the failure-to-stop alert" ) );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Server is running, so stop it.
|
|||
|
if( [self stopFirefly] )
|
|||
|
{
|
|||
|
[self updateServerStatus:kFireflyStatusStopping];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[self updateServerStatus:kFireflyStatusInvalid];
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Unable to stop Firefly",
|
|||
|
@"Alert message notifying the user of failure to stop" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NSLocalizedString( @"An unexpected error occurred when trying to stop Firefly. "
|
|||
|
"Please close and re-open this Preference pane, and try again.",
|
|||
|
@"Explanatory text for the failure-to-stop alert" ) );
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// webPageButtonClicked:
|
|||
|
//
|
|||
|
// User clicked the Open Web Page button, so we want to open the server's
|
|||
|
// config page.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)webPageButtonClicked:(id)sender
|
|||
|
{
|
|||
|
// User clicked the Show web page button. Open the firefly internal page.
|
|||
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:serverURL]];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// applyNowButtonClicked:
|
|||
|
//
|
|||
|
// Time to save our settings!
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)applyNowButtonClicked:(id)sender
|
|||
|
{
|
|||
|
if( [[[self mainView] window] makeFirstResponder:[[self mainView] window]] &&
|
|||
|
[self currentTabIsValid] )
|
|||
|
{
|
|||
|
if( [self saveSettings] )
|
|||
|
{
|
|||
|
[applyNowButton setEnabled:NO];
|
|||
|
if( [self fireflyIsRunning] )
|
|||
|
[self restartFirefly];
|
|||
|
}
|
|||
|
else
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Failed to apply changes",
|
|||
|
@"Alert message notifying the user of failure to apply" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NSLocalizedString( @"Due to an unexpected error, your changes could not "
|
|||
|
"be applied.",
|
|||
|
@"Explanatory text for the failure-to-apply alert" ) );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// helperMenuCheckboxClicked:
|
|||
|
//
|
|||
|
// User clicked the checkbox to show or hide the firefly menu. This happens
|
|||
|
// right away. The helper writes the pref for us.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)helperMenuCheckboxClicked:(id)sender
|
|||
|
{
|
|||
|
if( NSOffState == [helperMenuCheckbox state] )
|
|||
|
[self showHelperMenu:NO];
|
|||
|
else
|
|||
|
[self showHelperMenu:YES];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// logoButtonClicked:
|
|||
|
//
|
|||
|
// User clicked the logo button, so we want to open the Firefly web site
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (IBAction)logoButtonClicked:(id)sender
|
|||
|
{
|
|||
|
// User clicked the Firefly logo in the prefs pane. Open the web page.
|
|||
|
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:@"http://fireflymediaserver.org"]];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// controlTextDidChange:
|
|||
|
//
|
|||
|
// If the text in the control changes at all (the first time a user adds
|
|||
|
// or removes a character), we want to mark the configuration as needing
|
|||
|
// to be saved
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)controlTextDidChange:(NSNotification *)notification
|
|||
|
{
|
|||
|
// If any of our text fields have changed text, we need to enable the "Apply" button.
|
|||
|
[self setConfigNeedsSaving:YES];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// validateInstall:
|
|||
|
//
|
|||
|
// Called when the prefs pane is first being loaded. Locates the pieces we
|
|||
|
// need to do our work (specifically, the config file and the Firefly Helper
|
|||
|
// application). Creates the Firefly directory and a default config file
|
|||
|
// if none is present. Makes note of any errors encountered for reporting
|
|||
|
// to the user when the panel finishes loading.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)validateInstall
|
|||
|
{
|
|||
|
configAppearsValid = NO;
|
|||
|
|
|||
|
do // while( false )
|
|||
|
{
|
|||
|
// First up, locate or create the Firefly directory in Application Support.
|
|||
|
NSFileManager *mgr = [NSFileManager defaultManager];
|
|||
|
NSArray * appSupportDirArray = nil;
|
|||
|
NSString * appSupportPath = nil;
|
|||
|
|
|||
|
// If we were guaranteed to be on 10.4 or later, we could call this:
|
|||
|
//appSupportDirArray = NSSearchPathForDirectoriesInDomains( NSApplicationSupportDirectory,
|
|||
|
// NSUserDomainMask,
|
|||
|
// YES );
|
|||
|
|
|||
|
// But, we're not on 10.4; we have to go back to 10.3. So, we look in
|
|||
|
// the Library directory.
|
|||
|
appSupportDirArray = NSSearchPathForDirectoriesInDomains( NSLibraryDirectory,
|
|||
|
NSUserDomainMask,
|
|||
|
YES );
|
|||
|
if( [appSupportDirArray count] > 0 )
|
|||
|
{
|
|||
|
appSupportPath = [[appSupportDirArray objectAtIndex:0]
|
|||
|
stringByAppendingPathComponent:@"Application Support"];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[configError setString:NSLocalizedString( @"Library directory could not be found in user folder",
|
|||
|
@"Error message displayed at panel load" )];
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
BOOL isDir = YES;
|
|||
|
if( ![mgr fileExistsAtPath:appSupportPath isDirectory:&isDir] || !isDir )
|
|||
|
{
|
|||
|
BOOL bFail = YES;
|
|||
|
// If this is still true, it means that the directory is missing
|
|||
|
// (Otherwise, there's a *file* called Application Support here!
|
|||
|
if( isDir )
|
|||
|
{
|
|||
|
bFail = ( 0 != mkdir( [mgr fileSystemRepresentationWithPath:appSupportPath],
|
|||
|
0755 ) );
|
|||
|
}
|
|||
|
|
|||
|
if( bFail )
|
|||
|
{
|
|||
|
[configError setString:NSLocalizedString( @"Unable to find or create Application Support folder",
|
|||
|
@"Error message displayed at panel load" )];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[fireflyFolderPath setString:[appSupportPath stringByAppendingPathComponent:@FIREFLY_DIR_NAME]];
|
|||
|
if( ![mgr fileExistsAtPath:fireflyFolderPath isDirectory:&isDir] || !isDir )
|
|||
|
{
|
|||
|
// As above, except that this is less unexpected
|
|||
|
BOOL bFail = YES;
|
|||
|
// If this is still true, it means that the directory is missing
|
|||
|
// (Otherwise, there's a *file* called Application Support here!
|
|||
|
if( isDir )
|
|||
|
{
|
|||
|
bFail = ( 0 != mkdir( [mgr fileSystemRepresentationWithPath:fireflyFolderPath],
|
|||
|
0755 ) );
|
|||
|
}
|
|||
|
|
|||
|
if( bFail )
|
|||
|
{
|
|||
|
// We're done. If we can't find the Firefly directory, notify the user and disable
|
|||
|
// everything. Yes, maybe the server might be running and we could locate it, but somebody
|
|||
|
// who has installed the server in a non-standard location doesn't need us.
|
|||
|
NSString *formatString = NSLocalizedString( @"Firefly directory could not be found or created at: %@",
|
|||
|
"Format string for error message" );
|
|||
|
[configError setString:[NSString stringWithFormat:formatString, fireflyFolderPath]];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Check for the config file
|
|||
|
[configFilePath setString:[fireflyFolderPath stringByAppendingPathComponent:@FIREFLY_CONF_NAME]];
|
|||
|
if( [mgr fileExistsAtPath:configFilePath] )
|
|||
|
{
|
|||
|
// It exists. Can we write to it?
|
|||
|
if( ![mgr isWritableFileAtPath:configFilePath] )
|
|||
|
{
|
|||
|
// This is bad. If we can't write to the config file, all we can do is open the web page and
|
|||
|
// start/stop the server
|
|||
|
NSString *formatString = NSLocalizedString( @"The configuration file is present, but is not writable: %@",
|
|||
|
"Format string for error message" );
|
|||
|
[configError setString:[NSString stringWithFormat:formatString, configFilePath]];
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// No config file, so let's create the default one
|
|||
|
if( ![self createDefaultConfigFile] )
|
|||
|
{
|
|||
|
// Fatal error. Alert the user and disable everything.
|
|||
|
NSString *formatString = NSLocalizedString( @"Unable to create a default configuration file at: %@",
|
|||
|
"Format string for error message upon invalid install" );
|
|||
|
[configError setString:[NSString stringWithFormat:formatString, configFilePath]];
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// This lets willSelect know that we wrote a new config file
|
|||
|
bConfigNeedsSaving = YES;
|
|||
|
}
|
|||
|
|
|||
|
// Check to make sure the helper app is present (also required)
|
|||
|
[fireflyHelperPath setString:[[NSBundle bundleForClass:[self class]] pathForResource:@FIREFLY_HELPER_NAME
|
|||
|
ofType:nil]];
|
|||
|
if( ![mgr isExecutableFileAtPath:fireflyHelperPath] )
|
|||
|
{
|
|||
|
// As above, this is a fatal error
|
|||
|
[configError setString:NSLocalizedString( @"The Firefly installation appears to be damaged. Unable to"
|
|||
|
" locate Firefly Helper.",
|
|||
|
@"Format string for error message upon invalid install" )];
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
// Phew!
|
|||
|
configAppearsValid = YES;
|
|||
|
|
|||
|
} while( false );
|
|||
|
|
|||
|
return configAppearsValid;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// readConfigFromPath:
|
|||
|
//
|
|||
|
// Reading and writing the config file
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)readConfigFromPath:(NSString*)path
|
|||
|
{
|
|||
|
// I'm sure there's a nice Cocoa/Carbon/MacOS way to do this, but the docs
|
|||
|
// are not forthcoming. So, we do it the Unix way.
|
|||
|
FILE *configFile = fopen( [path UTF8String], "r" );
|
|||
|
if( NULL == configFile )
|
|||
|
return NO;
|
|||
|
|
|||
|
// Set up our members in case we've been run before
|
|||
|
idxOfServerName = 0;
|
|||
|
idxOfPassword = 0;
|
|||
|
idxOfPort = 0;
|
|||
|
idxOfLibraryPath = 0;
|
|||
|
idxOfNextSection = 0;
|
|||
|
idxOfDbPath = 0;
|
|||
|
idxOfLogPath = 0;
|
|||
|
|
|||
|
// Now, read the file
|
|||
|
BOOL bInGeneral = NO;
|
|||
|
char buf[1024]; // yes, a hardcoded limit, but seriously, this is for one line.
|
|||
|
[configFileStrings removeAllObjects];
|
|||
|
|
|||
|
while( NULL != fgets( buf, 1024, configFile ) )
|
|||
|
{
|
|||
|
buf[1023] = 0;
|
|||
|
NSString *line = [NSString stringWithUTF8String:buf];
|
|||
|
[configFileStrings addObject:line];
|
|||
|
|
|||
|
// Check to see if this is one of the lines we care about
|
|||
|
if( 0 == idxOfNextSection )
|
|||
|
{
|
|||
|
if( bInGeneral )
|
|||
|
{
|
|||
|
if( 0 == idxOfServerName && 0 == strncasecmp( buf, "servername", 10 ) )
|
|||
|
{
|
|||
|
idxOfServerName = [configFileStrings count] - 1;
|
|||
|
[serverName setString:[self readValueFromBuf:buf startingAt:10 unescapeCommas:NO]];
|
|||
|
}
|
|||
|
else if( 0 == idxOfPassword && 0 == strncasecmp( buf, "password", 8 ) )
|
|||
|
{
|
|||
|
idxOfPassword = [configFileStrings count] - 1;
|
|||
|
[serverPassword setString:[self readValueFromBuf:buf startingAt:8 unescapeCommas:NO]];
|
|||
|
}
|
|||
|
else if( 0 == idxOfPort && 0 == strncasecmp( buf, "port", 4 ) )
|
|||
|
{
|
|||
|
idxOfPort = [configFileStrings count] - 1;
|
|||
|
NSString *tmp = [self readValueFromBuf:buf startingAt:4 unescapeCommas:NO];
|
|||
|
unsigned long num = atol( [tmp UTF8String] );
|
|||
|
if( num < 65536 )
|
|||
|
serverPort = num;
|
|||
|
}
|
|||
|
else if( 0 == idxOfLibraryPath && 0 == strncasecmp( buf, "mp3_dir", 7 ) )
|
|||
|
{
|
|||
|
idxOfLibraryPath = [configFileStrings count] - 1;
|
|||
|
[libraryPath setString:[self readValueFromBuf:buf startingAt:7 unescapeCommas:YES]];
|
|||
|
}
|
|||
|
else if( 0 == idxOfDbPath && 0 == strncasecmp( buf, "db_parms", 8 ) )
|
|||
|
{
|
|||
|
// We only save the index of this if we're going to need to write the path
|
|||
|
// out. We need to write it out if the string is empty (which means that
|
|||
|
// it's coming from a default file).
|
|||
|
NSString *string = [self readValueFromBuf:buf startingAt:8 unescapeCommas:NO];
|
|||
|
if( 0 == [string length] )
|
|||
|
idxOfDbPath = [configFileStrings count] - 1;
|
|||
|
}
|
|||
|
else if( 0 == idxOfLogPath && 0 == strncasecmp( buf, "logfile", 7 ) )
|
|||
|
{
|
|||
|
// as above
|
|||
|
[logFilePath setString:[self readValueFromBuf:buf startingAt:7 unescapeCommas:NO]];
|
|||
|
if( 0 == [logFilePath length] )
|
|||
|
idxOfLogPath = [configFileStrings count] - 1;
|
|||
|
}
|
|||
|
else if( 0 == idxOfPlaylistPath && 0 == strncasecmp( buf, "playlist", 8 ) )
|
|||
|
{
|
|||
|
// as above
|
|||
|
NSString *string = [self readValueFromBuf:buf startingAt:8 unescapeCommas:NO];
|
|||
|
if( 0 == [string length] )
|
|||
|
idxOfPlaylistPath = [configFileStrings count] - 1;
|
|||
|
}
|
|||
|
|
|||
|
else if( buf[0] == '[' )
|
|||
|
{
|
|||
|
idxOfNextSection = [configFileStrings count] - 1;
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if( 0 == strncasecmp( buf, "[general]", 9 ) )
|
|||
|
bInGeneral = YES;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fclose( configFile );
|
|||
|
return YES;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// WriteCommaEscapedStringToFile:
|
|||
|
//
|
|||
|
// Utility function. Takes a const char* as input and copies the string,
|
|||
|
// escaping any commas as ",,". Writes the resulting string to the
|
|||
|
// supplied FILE*, which is assumed to be open for writing
|
|||
|
//
|
|||
|
// NOTE: This function could be smarter. For example, if a string
|
|||
|
// is passed in that is 1023 characters in length but contains commas,
|
|||
|
// this function will truncate the string silently.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
static void
|
|||
|
WriteCommaEscapedStringToFile( FILE *inFile, const char *inStringToEscape )
|
|||
|
{
|
|||
|
if( NULL == inFile || NULL == inStringToEscape )
|
|||
|
return;
|
|||
|
|
|||
|
char escapingBuf[1025]; // 1 extra in case of a final comma
|
|||
|
int i = 0;
|
|||
|
int j = 0;
|
|||
|
while( '\0' != inStringToEscape[i] && j < 1023 )
|
|||
|
{
|
|||
|
// Extra comma for any comma we find
|
|||
|
if( ',' == inStringToEscape[i] )
|
|||
|
escapingBuf[j++] = ',';
|
|||
|
escapingBuf[j++] = inStringToEscape[i++];
|
|||
|
}
|
|||
|
escapingBuf[j] = '\0';
|
|||
|
fputs( escapingBuf, inFile );
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// writeConfigToPath:
|
|||
|
//
|
|||
|
// Writes our configuration to the supplied path. readConfigFromPath
|
|||
|
// MUST have been called first!
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)writeConfigToPath:(NSString*)path
|
|||
|
{
|
|||
|
if( nil == configFileStrings || 0 == [configFileStrings count] )
|
|||
|
return NO;
|
|||
|
|
|||
|
FILE *configFile = fopen( [path UTF8String], "w" );
|
|||
|
if( NULL == configFile )
|
|||
|
return NO;
|
|||
|
|
|||
|
char buf[1024];
|
|||
|
|
|||
|
unsigned i = 0;
|
|||
|
for( ; i < [configFileStrings count]; i++ )
|
|||
|
{
|
|||
|
if( 0 == i )
|
|||
|
{
|
|||
|
// 0 is special-cased since it's also a special token for "this line wasn't
|
|||
|
// in the original". Since we only use the new file format, though, we're guaranteed
|
|||
|
// that none of our tokens is at the first (0th) line, because [general] has to
|
|||
|
// come before any of our tokens
|
|||
|
fputs( [[configFileStrings objectAtIndex:0] UTF8String], configFile );
|
|||
|
}
|
|||
|
else if( i == idxOfNextSection )
|
|||
|
{
|
|||
|
// We've reached the end of our general section, so it's now time to write
|
|||
|
// out anything that the user has set, but that wasn't found in the config
|
|||
|
// file before. Note that this is basically error recovery for somebody
|
|||
|
// mucking with the file, since all lines should be present but with emtpy
|
|||
|
// values if an optional setting isn't set. For example:
|
|||
|
// password =
|
|||
|
// If we didn't find it before, its index will be 0.
|
|||
|
|
|||
|
// servername is required
|
|||
|
if( 0 == idxOfServerName )
|
|||
|
{
|
|||
|
sprintf( buf, "servername = %s\n", [serverName UTF8String] );
|
|||
|
fputs( buf, configFile );
|
|||
|
}
|
|||
|
|
|||
|
// so is the library path
|
|||
|
if( 0 == idxOfLibraryPath )
|
|||
|
{
|
|||
|
sprintf( buf, "mp3_dir = %s\n", [libraryPath UTF8String] );
|
|||
|
WriteCommaEscapedStringToFile( configFile, buf );
|
|||
|
}
|
|||
|
|
|||
|
// password and port are optional, so don't write an empty entry
|
|||
|
if( 0 == idxOfPassword && [serverPassword length] > 0 )
|
|||
|
{
|
|||
|
sprintf( buf, "password = %s\n", [serverPassword UTF8String] );
|
|||
|
fputs( buf, configFile );
|
|||
|
}
|
|||
|
if( 0 == idxOfPort && 0 != serverPort )
|
|||
|
{
|
|||
|
sprintf( buf, "port = %u\n", serverPort );
|
|||
|
fputs( buf, configFile );
|
|||
|
}
|
|||
|
|
|||
|
// Don't forget the section header for that next section!
|
|||
|
fputs( [[configFileStrings objectAtIndex:i] UTF8String], configFile );
|
|||
|
}
|
|||
|
else if( i == idxOfServerName )
|
|||
|
{
|
|||
|
sprintf( buf, "servername = %s\n", [serverName UTF8String] );
|
|||
|
fputs( buf, configFile );
|
|||
|
}
|
|||
|
else if( i == idxOfPassword ) // NOTE: This will write an empty password if none is set
|
|||
|
{
|
|||
|
sprintf( buf, "password = %s\n", [serverPassword UTF8String] );
|
|||
|
fputs( buf, configFile );
|
|||
|
}
|
|||
|
else if( i == idxOfPort )
|
|||
|
{
|
|||
|
if( 0 == serverPort )
|
|||
|
fputs( "port =\n", configFile ); // no port is set, so write an empty value
|
|||
|
else
|
|||
|
{
|
|||
|
sprintf( buf, "port = %u\n", serverPort );
|
|||
|
fputs( buf, configFile );
|
|||
|
}
|
|||
|
}
|
|||
|
else if( i == idxOfLibraryPath )
|
|||
|
{
|
|||
|
sprintf( buf, "mp3_dir = %s\n", [libraryPath UTF8String] );
|
|||
|
WriteCommaEscapedStringToFile( configFile, buf );
|
|||
|
}
|
|||
|
else if ( i == idxOfDbPath )
|
|||
|
{
|
|||
|
sprintf( buf, "db_parms = %s\n", [fireflyFolderPath UTF8String] );
|
|||
|
WriteCommaEscapedStringToFile( configFile, buf );
|
|||
|
}
|
|||
|
else if( i == idxOfLogPath )
|
|||
|
{
|
|||
|
sprintf( buf, "logfile = %s\n", [logFilePath UTF8String] );
|
|||
|
WriteCommaEscapedStringToFile( configFile, buf );
|
|||
|
}
|
|||
|
else if( i == idxOfPlaylistPath )
|
|||
|
{
|
|||
|
sprintf( buf, "playlist = %s\n", [playlistPath UTF8String] );
|
|||
|
WriteCommaEscapedStringToFile( configFile, buf );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Just output our stored line
|
|||
|
fputs( [[configFileStrings objectAtIndex:i] UTF8String], configFile );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
fclose( configFile );
|
|||
|
[self setConfigNeedsSaving:NO];
|
|||
|
return YES;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// readValueFromBuf:startingAt:unescapeCommas:
|
|||
|
//
|
|||
|
// Read the value from a key/value pair string. idx is the start point, which
|
|||
|
// is assumed to be the next character after the key. NOTE that this function
|
|||
|
// may modify buf if it contains trailing whitespace.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (NSString *)readValueFromBuf:(char*)buf startingAt:(int)idx unescapeCommas:(BOOL)bUnescapeCommas
|
|||
|
{
|
|||
|
char *retVal = NULL;
|
|||
|
BOOL bFoundEquals = NO;
|
|||
|
|
|||
|
// skip over whitespace and = characters
|
|||
|
while( buf[idx] )
|
|||
|
{
|
|||
|
if( buf[idx] == '=' )
|
|||
|
bFoundEquals = YES;
|
|||
|
else if( buf[idx] != ' ' && buf[idx] != '\t' )
|
|||
|
break;
|
|||
|
|
|||
|
idx++;
|
|||
|
}
|
|||
|
|
|||
|
// Okay, we found whitespace or the end of the line. If we didn't find an equals sign, then the
|
|||
|
// value is empty. If there's nothing there but a newline, that's also considered empty.
|
|||
|
if( !bFoundEquals || buf[idx] == '\0' || buf[idx] == '\n' || buf[idx] == '\r' )
|
|||
|
return [NSString string];
|
|||
|
|
|||
|
// We found an equals, so retVal will point at our string to return. Now it's time
|
|||
|
// to clip off any trailing whitespace. We work back from the end of the line, since
|
|||
|
// whitespace is permitted in some places like the path and server name.
|
|||
|
|
|||
|
retVal = &buf[idx];
|
|||
|
idx = strlen( retVal ); // this is at least 1, because we tested for empty above
|
|||
|
|
|||
|
while( idx-- && (retVal[idx] == ' ' || retVal[idx] == '\t' || retVal[idx] == '\r' || retVal[idx] == '\n') )
|
|||
|
retVal[idx] = '\0';
|
|||
|
|
|||
|
// And, finally, if bUnescapeCommas is true, walk the string and
|
|||
|
// convert ",," to "," in place
|
|||
|
if( bUnescapeCommas)
|
|||
|
{
|
|||
|
int readIdx = 0;
|
|||
|
int writeIdx = 0;
|
|||
|
while( '\0' != retVal[readIdx] )
|
|||
|
{
|
|||
|
if( ',' == retVal[readIdx] && ',' == retVal[readIdx+1] )
|
|||
|
readIdx++;
|
|||
|
retVal[writeIdx++] = retVal[readIdx++];
|
|||
|
}
|
|||
|
retVal[writeIdx] = '\0';
|
|||
|
}
|
|||
|
|
|||
|
return [NSString stringWithUTF8String:retVal];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// createDefaultConfigFile:
|
|||
|
//
|
|||
|
// Read the value from a key/value pair string. idx is the start point, which
|
|||
|
// is assumed to be the next character after the key. NOTE that this function
|
|||
|
// may modify buf if it contains trailing whitespace.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)createDefaultConfigFile
|
|||
|
{
|
|||
|
// Read the default file from our bundle
|
|||
|
NSString *configPath = nil;
|
|||
|
NSBundle *thisBundle = [NSBundle bundleForClass:[self class]];
|
|||
|
if( nil == (configPath = [thisBundle pathForResource:@FIREFLY_CONF_NAME ofType:nil]) ||
|
|||
|
![self readConfigFromPath:configPath] )
|
|||
|
return NO;
|
|||
|
|
|||
|
// Set the default values. This takes the items that are deliberately
|
|||
|
// left blank in the "starter" config file (because they're specific
|
|||
|
// to the particular installation location) and fills them in.
|
|||
|
[self setDefaultValues];
|
|||
|
|
|||
|
// Write it out
|
|||
|
return [self writeConfigToPath:configFilePath];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// setDefaultValues:
|
|||
|
//
|
|||
|
// Model utility sets up the members representing server configuration to
|
|||
|
// their proper initial defaults (which are host- and user-specific!)
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)setDefaultValues
|
|||
|
{
|
|||
|
// easy ones first
|
|||
|
serverPort = 0;
|
|||
|
[serverPassword setString:@""]; // FIXME: really no better way to clear a string?
|
|||
|
bStartServerOnLogin = YES;
|
|||
|
|
|||
|
// Get the host name and make the default library name in a localization-friendly way
|
|||
|
NSString *hostname = (NSString*)CSCopyMachineName();
|
|||
|
NSString *format = NSLocalizedString( @"%@'s Firefly on %@",
|
|||
|
@"Format string for default library name" );
|
|||
|
|
|||
|
[serverName setString:[NSString stringWithFormat:format, userName, hostname]];
|
|||
|
[hostname release];
|
|||
|
|
|||
|
// Defaults for the log file and playlist paths. These get used only
|
|||
|
// when we first write out our default config file.
|
|||
|
[logFilePath setString:[fireflyFolderPath stringByAppendingPathComponent:
|
|||
|
@FIREFLY_LOG_FILE]];
|
|||
|
[playlistPath setString:[fireflyFolderPath stringByAppendingPathComponent:
|
|||
|
@FIREFLY_PLAYLIST_FILE]];
|
|||
|
|
|||
|
// Finally, the default Music directory
|
|||
|
[libraryPath setString:[@"~/Music" stringByExpandingTildeInPath]];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// setConfigNeedsSaving:
|
|||
|
//
|
|||
|
// Tracking the need to save the config.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
-(void)setConfigNeedsSaving:(BOOL)needsSaving
|
|||
|
{
|
|||
|
[applyNowButton setEnabled:needsSaving];
|
|||
|
bConfigNeedsSaving = needsSaving;
|
|||
|
}
|
|||
|
|
|||
|
// ===========================================================================
|
|||
|
// UI utility functions for setting the UI into certain common states
|
|||
|
// (sets up control enabling, text, etc.)
|
|||
|
// ===========================================================================
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// disableAllControls:
|
|||
|
//
|
|||
|
// Disables all the controls in the prefs pane. Used when the configuration
|
|||
|
// is invalid.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)disableAllControls
|
|||
|
{
|
|||
|
[browseButton setEnabled:false];
|
|||
|
[libraryField setEnabled:false];
|
|||
|
[nameField setEnabled:false];
|
|||
|
[passwordCheckbox setEnabled:false];
|
|||
|
[passwordField setEnabled:false];
|
|||
|
[portField setEnabled:false];
|
|||
|
[serverStartOptions setEnabled:false];
|
|||
|
[startStopButton setEnabled:false];
|
|||
|
[webPageButton setEnabled:false];
|
|||
|
[helperMenuCheckbox setEnabled:false];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// updateServerStatus:
|
|||
|
//
|
|||
|
// Handles updating all relevant UI elements according to the server status
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)updateServerStatus:(FireflyServerStatus) status
|
|||
|
{
|
|||
|
BOOL bAnimateProgress = NO;
|
|||
|
BOOL bActivateStartStop = YES;
|
|||
|
BOOL bButtonIsStart = YES;
|
|||
|
BOOL bClearWebAndVersion = NO;
|
|||
|
|
|||
|
switch( status )
|
|||
|
{
|
|||
|
case kFireflyStatusStartFailed:
|
|||
|
case kFireflyStatusCrashed:
|
|||
|
case kFireflyStatusStopped:
|
|||
|
bClearWebAndVersion = YES;
|
|||
|
break;
|
|||
|
|
|||
|
case kFireflyStatusRestarting:
|
|||
|
case kFireflyStatusStarting:
|
|||
|
bAnimateProgress = YES;
|
|||
|
bButtonIsStart = NO;
|
|||
|
break;
|
|||
|
|
|||
|
case kFireflyStatusActive:
|
|||
|
case kFireflyStatusScanning:
|
|||
|
bButtonIsStart = NO;
|
|||
|
break;
|
|||
|
|
|||
|
case kFireflyStatusStopping:
|
|||
|
bActivateStartStop = NO;
|
|||
|
bAnimateProgress = YES;
|
|||
|
break;
|
|||
|
|
|||
|
case kFireflyStatusInvalid:
|
|||
|
default:
|
|||
|
bActivateStartStop = NO;
|
|||
|
bClearWebAndVersion = YES;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
[startStopButton setEnabled:bActivateStartStop];
|
|||
|
[statusText setStringValue:StringForFireflyStatus(status)];
|
|||
|
if( bAnimateProgress )
|
|||
|
[progressSpinner startAnimation:self];
|
|||
|
else
|
|||
|
[progressSpinner stopAnimation:self];
|
|||
|
|
|||
|
if( bButtonIsStart )
|
|||
|
[startStopButton setTitle:NSLocalizedString( @"Start Firefly",
|
|||
|
@"One of several titles for the start/stop button" )];
|
|||
|
else
|
|||
|
[startStopButton setTitle:NSLocalizedString( @"Stop Firefly",
|
|||
|
@"One of several titles for the start/stop button" )];
|
|||
|
|
|||
|
if( bClearWebAndVersion )
|
|||
|
{
|
|||
|
[serverVersionText setStringValue:NSLocalizedString( @"(available when Firefly is running)",
|
|||
|
@"Displayed in place of server version when server "
|
|||
|
"is not running" )];
|
|||
|
[webPageButton setEnabled:NO];
|
|||
|
[webPageInfoText setStringValue:NSLocalizedString( @"Additional configuration options are "
|
|||
|
"available from Firefly's built-in web page. "
|
|||
|
"Available when Firefly is running.",
|
|||
|
@"Info text for the web page button when server "
|
|||
|
"is not running" )];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ===========================================================================
|
|||
|
// Alert delegate method(s)
|
|||
|
// ===========================================================================
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// alertDidEnd:returnCode:contextInfo:
|
|||
|
//
|
|||
|
// This is called for our "OK"-type alerts, and we don't need to do anything
|
|||
|
// extra.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)alertDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
|
|||
|
{
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// applySheetDidEnd:returnCode:contextInfo:
|
|||
|
//
|
|||
|
// Sheet delegate method, specially for the "apply changes" sheet. Depending
|
|||
|
// upon the user's answer, we may need to write out our config file and
|
|||
|
// restart the server. We definitely have to send the replyToShouldUnselect
|
|||
|
// message, since we deferred a reply in shouldUnselect, and that's what
|
|||
|
// prompts this sheet.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void) applySheetDidEnd:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo
|
|||
|
{
|
|||
|
BOOL bResponse = YES; // what do we say to shouldUnselect?
|
|||
|
|
|||
|
// We want the sheet closed in case we have to post another alert
|
|||
|
[[alert window] orderOut:self];
|
|||
|
|
|||
|
if( NSAlertSecondButtonReturn == returnCode ) // "Cancel" button
|
|||
|
{
|
|||
|
bResponse = NO;
|
|||
|
}
|
|||
|
else if( NSAlertThirdButtonReturn == returnCode ) // "Don't Apply" button
|
|||
|
{
|
|||
|
// bResponse is already YES
|
|||
|
}
|
|||
|
else if( NSAlertFirstButtonReturn == returnCode ) // "Apply" button
|
|||
|
{
|
|||
|
if( [self currentTabIsValid] )
|
|||
|
{
|
|||
|
bResponse = YES;
|
|||
|
if( ![self saveSettings] )
|
|||
|
{
|
|||
|
bResponse = NO;
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Failed to apply changes",
|
|||
|
@"Alert message notifying the user of failure to apply" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NSLocalizedString( @"Due to an unexpected error, your changes could not "
|
|||
|
"be applied.",
|
|||
|
@"Explanatory text for the failure-to-apply alert" ) );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// If the server is running, we need to restart it. Happily,
|
|||
|
// the Firefly Helper will take care of that, so we can
|
|||
|
// go ahead and exit
|
|||
|
if( [self fireflyIsRunning] )
|
|||
|
[self restartFirefly];
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// Our tab data wasn't valid, so now there's an alert on the
|
|||
|
// screen. Cancel closing the sheet.
|
|||
|
bResponse = NO;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
[self replyToShouldUnselect:bResponse];
|
|||
|
}
|
|||
|
|
|||
|
// ===========================================================================
|
|||
|
// Tab view delegate method(s)
|
|||
|
// ===========================================================================
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// -tabView:shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem
|
|||
|
//
|
|||
|
// Our job is to return false if the tab view should not be able to switch
|
|||
|
// panes. We shouldn't switch if there's invalid text in any of our
|
|||
|
// fields. By trying to get the main window to become first responder, we
|
|||
|
// make sure that any field with editing in process will call its delegate
|
|||
|
// to see if the field value is valid. If it's not, then it won't be able
|
|||
|
// to give up first responder status, and makeFirstResponder will return
|
|||
|
// false.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)tabView:(NSTabView *)tabView shouldSelectTabViewItem:(NSTabViewItem *)tabViewItem
|
|||
|
{
|
|||
|
BOOL bRetVal = [[[self mainView] window] makeFirstResponder:[[self mainView] window]];
|
|||
|
if( bRetVal )
|
|||
|
bRetVal = [self currentTabIsValid];
|
|||
|
|
|||
|
if( bRetVal )
|
|||
|
{
|
|||
|
// See whether we need to start or stop our log update timer
|
|||
|
if( 3 == [tabView numberOfTabViewItems] )
|
|||
|
{
|
|||
|
NSTabViewItem *logTab = [tabView tabViewItemAtIndex:2];
|
|||
|
if( [tabViewItem isEqual:logTab] )
|
|||
|
{
|
|||
|
// Update the view and start the timer
|
|||
|
[self updateLogTextView];
|
|||
|
logTimer = [[NSTimer scheduledTimerWithTimeInterval:1.0
|
|||
|
target:self
|
|||
|
selector:@selector(logTimerFired:)
|
|||
|
userInfo:nil
|
|||
|
repeats:YES] retain];
|
|||
|
|
|||
|
}
|
|||
|
else if( [[tabView selectedTabViewItem] isEqual:logTab] )
|
|||
|
{
|
|||
|
// stop timer
|
|||
|
[logTimer invalidate];
|
|||
|
[logTimer autorelease];
|
|||
|
logTimer = nil;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return bRetVal;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// control:isValidObject
|
|||
|
//
|
|||
|
// NSControl delegate method, called when a control is about to commit its
|
|||
|
// newly-edited value. We return NO if the new value is not allowed, so
|
|||
|
// it will not allow editing to leave.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)control:(NSControl *)control isValidObject:(id) obj
|
|||
|
{
|
|||
|
BOOL bRetVal = YES;
|
|||
|
if( [obj isKindOfClass:[NSString class]] )
|
|||
|
{
|
|||
|
NSString *string = (NSString *)obj;
|
|||
|
if( control == portField )
|
|||
|
{
|
|||
|
bRetVal = ( 1023 < [string intValue] && 65536 > [string intValue] );
|
|||
|
}
|
|||
|
else if( control == nameField )
|
|||
|
{
|
|||
|
bRetVal = ( 0 < [string length] );
|
|||
|
}
|
|||
|
else if( control == passwordField )
|
|||
|
{
|
|||
|
bRetVal = ( NSOffState == [passwordCheckbox state] || 0 < [string length] );
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if( NO == bRetVal )
|
|||
|
[self alertForControl:control];
|
|||
|
|
|||
|
return bRetVal;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// currentTabIsValid
|
|||
|
//
|
|||
|
// Here's the deal. Annoyingly, Cocoa text fields don't call their delegate
|
|||
|
// methods when they lose focus *if* they haven't changed. So, this method
|
|||
|
// is our backstop, in case the other delegates don't catch this case. It
|
|||
|
// figures out the current tab, and then checks that the fields are valid.
|
|||
|
//
|
|||
|
// NOTE: We assume that the window has already been set to the first
|
|||
|
// responder, so that we can query the controls directly for their values.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)currentTabIsValid
|
|||
|
{
|
|||
|
BOOL bRetVal = YES;
|
|||
|
NSTabViewItem *selectedTab = [mainTabView selectedTabViewItem];
|
|||
|
int idx;
|
|||
|
if( nil != selectedTab )
|
|||
|
{
|
|||
|
idx = [mainTabView indexOfTabViewItem:selectedTab];
|
|||
|
if( 0 == idx )
|
|||
|
{
|
|||
|
// General
|
|||
|
if( ! (bRetVal = [self control:nameField isValidObject:[nameField objectValue]]) )
|
|||
|
[self alertForControl:nameField];
|
|||
|
else if( ! (bRetVal = [self control:passwordField isValidObject:[passwordField objectValue]]) )
|
|||
|
[self alertForControl:nameField];
|
|||
|
}
|
|||
|
else if( 1 == idx )
|
|||
|
{
|
|||
|
// Advanced
|
|||
|
// If "Manual" is selected, but the value of the field is not kosher, we must say no
|
|||
|
if( 1 == [portPopup indexOfSelectedItem] &&
|
|||
|
!(bRetVal = [self control:portField isValidObject:[portField objectValue]]) )
|
|||
|
[self alertForControl:portField];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return bRetVal;
|
|||
|
}
|
|||
|
|
|||
|
// ========================================================================
|
|||
|
// Private utilities
|
|||
|
// ========================================================================
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// alertForControl
|
|||
|
//
|
|||
|
// There are a couple of places where we may need to pop up a modal alert
|
|||
|
// because a control's value is not valid. So, we have this utility. It
|
|||
|
// displays a modal "OK" style alert sheet with text specific to the
|
|||
|
// control, then returns.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)alertForControl:(NSControl *)control
|
|||
|
{
|
|||
|
NSString *alertTitle;
|
|||
|
NSString *alertMessage;
|
|||
|
if( control == nameField )
|
|||
|
{
|
|||
|
alertTitle = NSLocalizedString( @"Missing library name", "@Alert title when library name is invalid" );
|
|||
|
alertMessage = NSLocalizedString( @"Please enter a library name",
|
|||
|
@"Error message if library name is invalid" );
|
|||
|
}
|
|||
|
else if( control == passwordField )
|
|||
|
{
|
|||
|
alertTitle = NSLocalizedString( @"Missing password", "@Alert title when password is invalid" );
|
|||
|
alertMessage = NSLocalizedString( @"Please enter a password, or un-check the password checkbox",
|
|||
|
@"Error message if password is empty" );
|
|||
|
}
|
|||
|
else if( control == portField )
|
|||
|
{
|
|||
|
alertTitle = NSLocalizedString( @"Invalid port number", "@Alert title when port number is invalid" );
|
|||
|
alertMessage = NSLocalizedString( @"Please enter a port number between 1024 and 65535, or choose "
|
|||
|
"\"Automatic\" from the pop-up menu",
|
|||
|
@"Error message if invalid port entered" );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
alertTitle = NSLocalizedString( @"Invalid value", @"Generic alert string for an invalid control" );
|
|||
|
alertMessage = @"";
|
|||
|
}
|
|||
|
|
|||
|
NSBeginAlertSheet( alertTitle,
|
|||
|
@"OK", NULL, NULL, [[self mainView] window],
|
|||
|
nil, NULL, NULL, NULL,
|
|||
|
alertMessage );
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// setIconForPath
|
|||
|
//
|
|||
|
// This function takes our library path and sets the icon in the Advanced tab
|
|||
|
// to be that path's icon. It's complicated a bit by the need to have a
|
|||
|
// special icon in case the path can't be found.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)setIconForPath
|
|||
|
{
|
|||
|
NSFileManager *mgr = [NSFileManager defaultManager];
|
|||
|
BOOL isDir = NO;
|
|||
|
if( [mgr fileExistsAtPath:libraryPath isDirectory:&isDir] && isDir )
|
|||
|
{
|
|||
|
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
|||
|
[libraryIcon setImage:[workspace iconForFile:libraryPath]];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// we want a default "?" image, and IconServices is kind enough to oblige
|
|||
|
IconRef unknownIcon;
|
|||
|
if( 0 == GetIconRef( kOnSystemDisk, kSystemIconsCreator, kUnknownFSObjectIcon, &unknownIcon ) )
|
|||
|
{
|
|||
|
NSImage* image = [[NSImage alloc] initWithSize:NSMakeSize(32,32)];
|
|||
|
[image lockFocus];
|
|||
|
CGRect iconRect = CGRectMake(0,0,32,32);
|
|||
|
PlotIconRefInContext((CGContextRef)[[NSGraphicsContext currentContext] graphicsPort],
|
|||
|
&iconRect,
|
|||
|
kAlignNone,
|
|||
|
kTransformNone,
|
|||
|
NULL /*labelColor*/,
|
|||
|
kPlotIconRefNormalFlags,
|
|||
|
unknownIcon);
|
|||
|
[image unlockFocus];
|
|||
|
[libraryIcon setImage:image];
|
|||
|
[image release];
|
|||
|
ReleaseIconRef( unknownIcon );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[libraryIcon setImage:nil];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// loadSettings
|
|||
|
//
|
|||
|
// Read the config file and also fetch our server startup preference from
|
|||
|
// MacOS's preference mechanism
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)loadSettings
|
|||
|
{
|
|||
|
[self readSettingsForHelper:&bShowHelperMenu
|
|||
|
andServer:&bStartServerOnLogin];
|
|||
|
return [self readConfigFromPath:configFilePath];
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// saveSettings
|
|||
|
//
|
|||
|
// Writes out the config file and sets our prefs for whether we need to
|
|||
|
// launch the server at login
|
|||
|
//
|
|||
|
// Returns NO if this fails.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)saveSettings
|
|||
|
{
|
|||
|
BOOL bSuccess = NO;
|
|||
|
|
|||
|
CFPreferencesSetAppValue( CFSTR(FF_PREFS_LAUNCH_AT_LOGIN),
|
|||
|
bStartServerOnLogin ? kCFBooleanTrue : kCFBooleanFalse,
|
|||
|
CFSTR(FF_PREFS_DOMAIN) );
|
|||
|
|
|||
|
// Now the server config file
|
|||
|
bSuccess = [self writeConfigToPath:configFilePath];
|
|||
|
return bSuccess;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// updateLoginItem
|
|||
|
//
|
|||
|
// Based upon the current state of the persistent prefs (NOT our locals),
|
|||
|
// either set or un-set the Helper as a login item.
|
|||
|
//
|
|||
|
// NOTE: If bStartOnLogin is true, or if bShowMenu is true, then
|
|||
|
// we want the firefly helper to be in the startup items (because it handles
|
|||
|
// both of those tasks). But, if bShowMenu is true and bStartOnLogin
|
|||
|
// isn't, we'll start the helper but not the server.
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (BOOL)updateLoginItem
|
|||
|
{
|
|||
|
BOOL bSuccess = NO;
|
|||
|
NSString *scriptSource = nil;
|
|||
|
BOOL bStartOnLogin = NO;
|
|||
|
BOOL bShowMenu = NO;
|
|||
|
[self readSettingsForHelper:&bShowMenu andServer:&bStartOnLogin];
|
|||
|
if( bStartOnLogin || bShowMenu )
|
|||
|
{
|
|||
|
scriptSource = [NSString stringWithFormat:
|
|||
|
@"tell application \"System Events\"\n"
|
|||
|
"if \"Firefly Helper\" is not in (name of every login item) then\n"
|
|||
|
"make login item at end with properties {hidden:false, path:\"%@\"}\n"
|
|||
|
"end if\n"
|
|||
|
"end tell",
|
|||
|
fireflyHelperPath];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
scriptSource = [NSString stringWithFormat:
|
|||
|
@"tell application \"System Events\"\n"
|
|||
|
"if \"Firefly Helper\" is in (name of every login item) then\n"
|
|||
|
"delete (every login item whose name is \"Firefly Helper\")\n"
|
|||
|
"end if\n"
|
|||
|
"end tell\n"];
|
|||
|
}
|
|||
|
|
|||
|
NSDictionary *errorDict = nil;
|
|||
|
NSAppleScript *myScript = [[NSAppleScript alloc] initWithSource:scriptSource];
|
|||
|
|
|||
|
if( nil != myScript )
|
|||
|
{
|
|||
|
bSuccess = (nil != [myScript executeAndReturnError:&errorDict]);
|
|||
|
[myScript release];
|
|||
|
}
|
|||
|
return bSuccess;
|
|||
|
}
|
|||
|
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
// readSettingsForHelper:andServer:
|
|||
|
//
|
|||
|
// Utility to read the helper and server launch settings, since we need to
|
|||
|
// do it in more than one place
|
|||
|
// ---------------------------------------------------------------------------
|
|||
|
- (void)readSettingsForHelper:(BOOL*)outHelper andServer:(BOOL*)outServer
|
|||
|
{
|
|||
|
if( NULL != outHelper )
|
|||
|
{
|
|||
|
CFBooleanRef showHelper =
|
|||
|
CFPreferencesCopyAppValue( CFSTR(FF_PREFS_SHOW_MENU_EXTRA),
|
|||
|
CFSTR(FF_PREFS_DOMAIN) );
|
|||
|
if( nil != showHelper )
|
|||
|
{
|
|||
|
*outHelper = CFBooleanGetValue( showHelper );
|
|||
|
CFRelease( showHelper );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// default value
|
|||
|
*outHelper = NO;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if( NULL != outServer )
|
|||
|
{
|
|||
|
CFBooleanRef shouldLaunch =
|
|||
|
CFPreferencesCopyAppValue( CFSTR(FF_PREFS_LAUNCH_AT_LOGIN),
|
|||
|
CFSTR(FF_PREFS_DOMAIN) );
|
|||
|
if( nil != shouldLaunch )
|
|||
|
{
|
|||
|
*outServer = CFBooleanGetValue( shouldLaunch );
|
|||
|
CFRelease( shouldLaunch );
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// default value
|
|||
|
*outServer = YES;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// helperIsRunning
|
|||
|
//
|
|||
|
// Returns YES if "Firefly Helper" is running under our UID.
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (BOOL)helperIsRunning
|
|||
|
{
|
|||
|
bool bRetVal = NO;
|
|||
|
kinfo_proc *result;
|
|||
|
size_t length;
|
|||
|
GetProcesses( &result, &length );
|
|||
|
|
|||
|
// Okay, now we have our list of processes. Let's find OUR copy of
|
|||
|
// firefly. Note that Firefly runs as two processes, so we look
|
|||
|
// for the higher-numbered one.
|
|||
|
if( NULL != result )
|
|||
|
{
|
|||
|
int procCount = length / sizeof(kinfo_proc);
|
|||
|
int i = 0;
|
|||
|
uid_t ourUID = getuid();
|
|||
|
for( ; i < procCount; i++ )
|
|||
|
{
|
|||
|
if( ourUID == result[i].kp_eproc.e_pcred.p_ruid &&
|
|||
|
0 == strcasecmp( result[i].kp_proc.p_comm, FIREFLY_HELPER_PROC_N ) )
|
|||
|
{
|
|||
|
bRetVal = YES;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
free( result );
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
return bRetVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// launchHelperIfNeeded
|
|||
|
//
|
|||
|
// Checks to see if our helper app is already running. If not, launch
|
|||
|
// it using NSTask (which doesn't have the issue in NSWorkspace where
|
|||
|
// launching another app, even background-only, causes us to lose our
|
|||
|
// window focus!).
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)launchHelperIfNeeded
|
|||
|
{
|
|||
|
if( ![self helperIsRunning] )
|
|||
|
{
|
|||
|
NSBundle *bundle = [NSBundle bundleWithPath:fireflyHelperPath];
|
|||
|
NSString *path = [bundle executablePath];
|
|||
|
NSTask *task = [[NSTask alloc] init];
|
|||
|
[task setLaunchPath:path];
|
|||
|
[task launch];
|
|||
|
[task release];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// makeProxyConnection
|
|||
|
//
|
|||
|
// Try to connect up our serverProxy object by looking it up by name.
|
|||
|
// Returns a BOOL to indicate whether it has succeeded
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (BOOL)makeProxyConnection
|
|||
|
{
|
|||
|
BOOL bRetVal = NO;
|
|||
|
NSString *serviceName = [@"FireflyHelper" stringByAppendingString:(NSString*)userName];
|
|||
|
serverProxy = [NSConnection rootProxyForConnectionWithRegisteredName:serviceName
|
|||
|
host:nil];
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
// This will notify us if the helper quits out from under us
|
|||
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|||
|
selector:@selector(connectionDied:)
|
|||
|
name:NSConnectionDidDieNotification
|
|||
|
object:nil];
|
|||
|
|
|||
|
[serverProxy retain];
|
|||
|
[serverProxy setProtocolForProxy:@protocol(FireflyPrefsServerProtocol)];
|
|||
|
clientIdent = rand();
|
|||
|
[protocolChecker autorelease]; // in case we're being re-run
|
|||
|
protocolChecker = [[NSProtocolChecker
|
|||
|
protocolCheckerWithTarget:self
|
|||
|
protocol:@protocol(FireflyPrefsClientProtocol)] retain];
|
|||
|
|
|||
|
@try
|
|||
|
{
|
|||
|
bRetVal = [serverProxy registerClient:protocolChecker withIdentifier:clientIdent];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"makeProxyConnection caught %@: %@",
|
|||
|
[exception name], [exception reason]);
|
|||
|
}
|
|||
|
|
|||
|
// If we fail to register, we will ditch our server proxy and fail
|
|||
|
if( !bRetVal )
|
|||
|
{
|
|||
|
[serverProxy autorelease];
|
|||
|
serverProxy = nil;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return bRetVal;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// checkProxyConnection
|
|||
|
//
|
|||
|
// Checks to see if we have a valid proxy connection. If we don't,
|
|||
|
// this disables the controls in the panel, posts a dialog, and returns NO.
|
|||
|
//
|
|||
|
// Because of the dialog, this should only be called when a connection is
|
|||
|
// believed to exist.
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (BOOL)checkProxyConnection
|
|||
|
{
|
|||
|
BOOL bRetVal = NO;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
[serverProxy fireflyStatus];
|
|||
|
bRetVal = YES;
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"checkProxyConnection caught %@: %@",
|
|||
|
[exception name], [exception reason]);
|
|||
|
[serverProxy autorelease];
|
|||
|
serverProxy = nil;
|
|||
|
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Lost contact with Firefly Helper",
|
|||
|
@"Alert message notifying the user of failure to get status" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NSLocalizedString( @"Communication has been lost with the Firefly Helper. "
|
|||
|
"Please close and re-open this Preference pane, and try again.",
|
|||
|
@"Explanatory text for the connection-lost alert" ) );
|
|||
|
[self disableAllControls];
|
|||
|
[self updateServerStatus:kFireflyStatusInvalid];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return bRetVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// connectionDied
|
|||
|
//
|
|||
|
// This notification fires if an NSConnection dies. We don't bother to
|
|||
|
// save our server connection. Rather, if this notification comes in, we
|
|||
|
// check our connection.
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)connectionDied:(NSNotification *)notification
|
|||
|
{
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// proxyTimerFired
|
|||
|
//
|
|||
|
// If the helper wasn't ready when we first checked, we try once a second
|
|||
|
// for 10 seconds, using a timer
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)proxyTimerFired:(NSTimer *) timer
|
|||
|
{
|
|||
|
#ifdef FIREFLY_DEBUG
|
|||
|
NSBeep();
|
|||
|
#endif
|
|||
|
if( [self makeProxyConnection] )
|
|||
|
{
|
|||
|
[self updateServerStatus:[self fireflyStatus]];
|
|||
|
NSString *string = [self fireflyVersion];
|
|||
|
if( nil != string )
|
|||
|
[self versionChanged:string];
|
|||
|
string = [self fireflyConfigURL];
|
|||
|
if( nil != string )
|
|||
|
[self configUrlChanged:string];
|
|||
|
[ipcTimer invalidate];
|
|||
|
[ipcTimer autorelease];
|
|||
|
ipcTimer = nil;
|
|||
|
}
|
|||
|
else if( 10 < ++ipcTries )
|
|||
|
{
|
|||
|
[ipcTimer invalidate];
|
|||
|
[ipcTimer autorelease];
|
|||
|
ipcTimer = nil;
|
|||
|
[self updateServerStatus:kFireflyStatusInvalid];
|
|||
|
NSBeginCriticalAlertSheet( NSLocalizedString( @"Unable to get server status",
|
|||
|
@"Alert message notifying the user of failure to get status" ),
|
|||
|
@"OK",
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
[[self mainView] window],
|
|||
|
nil,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NULL,
|
|||
|
NSLocalizedString( @"An unexpected error occurred when trying to get the "
|
|||
|
"status of the Firefly server. "
|
|||
|
"Please close and re-open this Preference pane, and try again.",
|
|||
|
@"Explanatory text for the failure-to-get-status alert" ) );
|
|||
|
}
|
|||
|
// else we try again when the timer fires
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// updateLogTextView
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)updateLogTextView
|
|||
|
{
|
|||
|
NSFileManager *mgr = [NSFileManager defaultManager];
|
|||
|
if( [mgr isReadableFileAtPath:logFilePath] )
|
|||
|
{
|
|||
|
NSDictionary *dict = [mgr fileAttributesAtPath:logFilePath traverseLink:YES];
|
|||
|
NSDate *modDate = [dict objectForKey:NSFileModificationDate];
|
|||
|
if( nil != modDate && ![logDate isEqualTo:modDate] )
|
|||
|
{
|
|||
|
// log date is the last time we processed an update
|
|||
|
[logDate autorelease];
|
|||
|
logDate = [modDate retain];
|
|||
|
NSString *newContents = nil;
|
|||
|
NSData *data = [NSData dataWithContentsOfFile:logFilePath];
|
|||
|
if( nil != data );
|
|||
|
{
|
|||
|
newContents = [[NSString alloc]
|
|||
|
initWithData:data encoding:NSUTF8StringEncoding];
|
|||
|
}
|
|||
|
if( nil == newContents || 0 == [newContents length] )
|
|||
|
{
|
|||
|
[logTextView setString:NSLocalizedString( @"The log file is empty.",
|
|||
|
@"Text for empty log file" )];
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
// We're going to figure out our current scroll position and
|
|||
|
// the current selection (if any). After we set the text,
|
|||
|
// we'll re-select the same range, and if we were at the bottom
|
|||
|
// of the scroller, we'll make sure we stay there.
|
|||
|
NSRange selection = [logTextView selectedRange];
|
|||
|
float scrollPos = 0.0;
|
|||
|
scrollPos = [[[logTextView enclosingScrollView] verticalScroller] floatValue];
|
|||
|
|
|||
|
// Actually set the new text
|
|||
|
[logTextView setString:newContents];
|
|||
|
|
|||
|
// Restore selection (it's lost when setString is called)
|
|||
|
[logTextView setSelectedRange:selection];
|
|||
|
|
|||
|
// If we were previously scrolled to the end, scroll to the end.
|
|||
|
// Otherwise, leave the window as it is. (This makes a range
|
|||
|
// of the very end of the view).
|
|||
|
if( 1.0 == scrollPos )
|
|||
|
[logTextView scrollRangeToVisible:NSMakeRange([[logTextView string] length], 0)];
|
|||
|
}
|
|||
|
if( nil != newContents )
|
|||
|
[newContents autorelease];
|
|||
|
}
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
[logTextView setString:NSLocalizedString( @"The log file has not been created.",
|
|||
|
@"Text for missing log file" )];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// logTimerFired
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)logTimerFired:(NSTimer *) timer
|
|||
|
{
|
|||
|
[self updateLogTextView];
|
|||
|
}
|
|||
|
|
|||
|
// ========================================================================
|
|||
|
// These functions wrap our IPC calls, so we can catch the exception that
|
|||
|
// will be thrown if we try to access the server with the connection
|
|||
|
// broken
|
|||
|
// ========================================================================
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// startFirefly
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (FireflyStartResult)startFirefly
|
|||
|
{
|
|||
|
FireflyStartResult retVal = kFireflyStartFail;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy startFirefly];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"startFirefly caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// stopFirefly
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (FireflyStopResult)stopFirefly
|
|||
|
{
|
|||
|
FireflyStopResult retVal = kFireflyStopFail;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy stopFirefly];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"stopFirefly caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// restartFirefly
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (FireflyRestartResult)restartFirefly;
|
|||
|
{
|
|||
|
FireflyRestartResult retVal = kFireflyRestartFail;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy restartFirefly];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"restartFirefly caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// rescanLibrary
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (FireflyRescanResult)rescanLibrary;
|
|||
|
{
|
|||
|
FireflyRescanResult retVal = kFireflyRescanFail;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy rescanLibrary];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"rescanLibrary caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// fireflyStatus
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (FireflyServerStatus)fireflyStatus;
|
|||
|
{
|
|||
|
FireflyServerStatus retVal = kFireflyStatusInvalid;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy fireflyStatus];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"fireflyStatus caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// fireflyIsRunning
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (BOOL)fireflyIsRunning;
|
|||
|
{
|
|||
|
BOOL retVal = NO;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy fireflyIsRunning];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"fireflyStatus caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// fireflyVersion
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (NSString*)fireflyVersion;
|
|||
|
{
|
|||
|
NSString *retVal = nil;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy fireflyVersion];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"fireflyVersion caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// fireflyConfigURL
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (NSString*)fireflyConfigURL;
|
|||
|
{
|
|||
|
NSString *retVal = nil;
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
retVal = [serverProxy fireflyConfigURL];
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"fireflyConfigURL caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
}
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// showHelperMenu
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)showHelperMenu:(BOOL)bShowMenu
|
|||
|
{
|
|||
|
if( nil != serverProxy )
|
|||
|
{
|
|||
|
@try
|
|||
|
{
|
|||
|
[serverProxy showHelperMenu:bShowMenu];
|
|||
|
bShowHelperMenu = bShowMenu;
|
|||
|
CFPreferencesSetAppValue( CFSTR(FF_PREFS_SHOW_MENU_EXTRA),
|
|||
|
bShowMenu ? kCFBooleanTrue : kCFBooleanFalse,
|
|||
|
CFSTR(FF_PREFS_DOMAIN) );
|
|||
|
}
|
|||
|
@catch( NSException *exception )
|
|||
|
{
|
|||
|
NSLog(@"fireflyConfigURL caught %@: %@", [exception name], [exception reason]);
|
|||
|
[self checkProxyConnection];
|
|||
|
[helperMenuCheckbox setState:(bShowHelperMenu ? NSOnState : NSOffState)];
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
// ========================================================================
|
|||
|
// Implementation of the FireflyPrefsClientProtocol
|
|||
|
// ========================================================================
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// configUrlChanged
|
|||
|
//
|
|||
|
// We're being told that the server's configuration URL has changed
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)configUrlChanged:(NSString *)newUrl
|
|||
|
{
|
|||
|
if( 0 != [newUrl length] )
|
|||
|
{
|
|||
|
[webPageButton setEnabled:YES];
|
|||
|
[serverURL setString:newUrl];
|
|||
|
[webPageInfoText setStringValue:NSLocalizedString( @"Additional configuration options are "
|
|||
|
"available from Firefly's built-in web page. "
|
|||
|
"Click to open the page in your browser.",
|
|||
|
@"Info text for the web page button when server "
|
|||
|
"is running" )];
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// versionChanged
|
|||
|
//
|
|||
|
// We're being told that the server's version has changed
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)versionChanged:(NSString *)newVersion
|
|||
|
{
|
|||
|
if( 0 != [newVersion length] )
|
|||
|
[serverVersionText setStringValue:newVersion];
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// newStatus
|
|||
|
//
|
|||
|
// We're being told that the server's status has changed
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (void)statusChanged:(FireflyServerStatus)newStatus
|
|||
|
{
|
|||
|
[self updateServerStatus:newStatus];
|
|||
|
}
|
|||
|
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
// stillThere
|
|||
|
//
|
|||
|
// A "ping" to test the connection. If we received it, we're here!
|
|||
|
// ------------------------------------------------------------------------
|
|||
|
- (BOOL)stillThere
|
|||
|
{
|
|||
|
return YES;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
@end
|