diff --git a/Application/AppController.m b/Application/AppController.m index f3386b7de..40385aa6b 100644 --- a/Application/AppController.m +++ b/Application/AppController.m @@ -25,15 +25,13 @@ // Listen to the remote in exclusive mode, only when Cog is the active application - (void)applicationDidBecomeActive:(NSNotification *)notification { - BOOL onlyOnActive = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue]; - if (onlyOnActive) { + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"] && [[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"]) { [remote startListening: self]; } } - (void)applicationDidResignActive:(NSNotification *)motification { - BOOL onlyOnActive = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue]; - if (onlyOnActive) { + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"] && [[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"]) { [remote stopListening: self]; } } @@ -204,7 +202,7 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ [self registerHotKeys]; //Init Remote - if (![[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue]) { + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"] && ![[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"]) { [remote startListening:self]; } @@ -360,17 +358,30 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ [userDefaultsValuesDict setObject:[@"~/Music" stringByExpandingTildeInPath] forKey:@"fileDrawerRootPath"]; + [userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"remoteEnabled"]; [userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"remoteOnlyOnActive"]; + [userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"enableAudioScrobbler"]; + [userDefaultsValuesDict setObject:[NSNumber numberWithBool:YES] forKey:@"automaticallyLaunchLastFM"]; + //Register and sync defaults [[NSUserDefaults standardUserDefaults] registerDefaults:userDefaultsValuesDict]; [[NSUserDefaults standardUserDefaults] synchronize]; - + + //Add observers [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyPlayKeyCode" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyPreviousKeyCode" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.hotKeyNextKeyCode" options:0 context:nil]; [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil]; + + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.fileDrawerRootPath" options:0 context:nil]; + + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.remoteEnabled" options:0 context:nil]; + [[NSUserDefaultsController sharedUserDefaultsController] addObserver:self forKeyPath:@"values.remoteOnlyOnActive" options:0 context:nil]; } - (void) observeValueForKeyPath:(NSString *)keyPath @@ -390,16 +401,22 @@ increase/decrease as long as the user holds the left/right, plus/minus button */ else if ([keyPath isEqualToString:@"values.fileDrawerRootPath"]) { [fileTreeController setRootPath:[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"fileDrawerRootPath"]]; } - else if ([keyPath isEqualToString:@"values.remoteOnlyOnActive"]) { - BOOL onlyOnActive = [[[[NSUserDefaultsController sharedUserDefaultsController] defaults] objectForKey:@"remoteOnlyOnActive"] boolValue]; - if (!onlyOnActive || [NSApp isActive]) { - [remote startListening: self]; + else if ([keyPath isEqualToString:@"values.remoteEnabled"] || [keyPath isEqualToString:@"values.remoteOnlyOnActive"]) { + if([[NSUserDefaults standardUserDefaults] boolForKey:@"remoteEnabled"]) { + NSLog(@"Remote enabled..."); + BOOL onlyOnActive = [[NSUserDefaults standardUserDefaults] boolForKey:@"remoteOnlyOnActive"]; + if (!onlyOnActive || [NSApp isActive]) { + [remote startListening: self]; + } + if (onlyOnActive && ![NSApp isActive]) { //Setting a preference without being active? *shrugs* + [remote stopListening: self]; + } } - if (onlyOnActive && ![NSApp isActive]) { //Setting a preference without being active? *shrugs* + else { + NSLog(@"DISABLE REMOTE"); [remote stopListening: self]; } } - } - (void)registerHotKeys diff --git a/Application/PlaybackController.h b/Application/PlaybackController.h index 00d5aa3df..c7f140994 100644 --- a/Application/PlaybackController.h +++ b/Application/PlaybackController.h @@ -5,6 +5,7 @@ #import "CogAudio/AudioPlayer.h" #import "PlaylistController.h" #import "TrackingSlider.h" +#import "AudioScrobbler.h" @class PlaylistView; @@ -33,6 +34,8 @@ double currentVolume; BOOL showTimeRemaining; + + AudioScrobbler *scrobbler; } - (IBAction)toggleShowTimeRemaining:(id)sender; diff --git a/Application/PlaybackController.m b/Application/PlaybackController.m index cd4e48aa1..6a623c544 100644 --- a/Application/PlaybackController.m +++ b/Application/PlaybackController.m @@ -16,6 +16,8 @@ playbackStatus = kCogStatusStopped; showTimeRemaining = NO; + + scrobbler = [[AudioScrobbler alloc] init]; } return self; @@ -50,12 +52,21 @@ { // DBLog(@"Pause Sent!"); [audioPlayer pause]; + + if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler pause]; + } } - (IBAction)resume:(id)sender { // DBLog(@"Resume Sent!"); [audioPlayer resume]; + + + if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler resume]; + } } - (IBAction)stop:(id)sender @@ -63,6 +74,10 @@ // DBLog(@"Stop Sent!"); [audioPlayer stop]; + + if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler stop]; + } } //called by double-clicking on table @@ -81,7 +96,7 @@ [self playEntryAtIndex:[playlistView selectedRow]]; } -- (void)playEntry:(PlaylistEntry *)pe; +- (void)playEntry:(PlaylistEntry *)pe { // DBLog(@"PlayEntry: %@ Sent!", [pe filename]); if (playbackStatus != kCogStatusStopped) @@ -95,6 +110,10 @@ [audioPlayer play:[NSURL fileURLWithPath:[pe filename]] withUserInfo:pe]; [audioPlayer setVolume:currentVolume]; + + if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler start:pe]; + } } - (IBAction)next:(id)sender @@ -264,6 +283,9 @@ [self updateTimeField:0.0f]; + if([[NSUserDefaults standardUserDefaults] boolForKey:@"enableAudioScrobbler"]) { + [scrobbler start:pe]; + } } - (void)updatePosition:(id)sender diff --git a/AudioScrobbler/AudioScrobbler.h b/AudioScrobbler/AudioScrobbler.h new file mode 100644 index 000000000..3bcc4b237 --- /dev/null +++ b/AudioScrobbler/AudioScrobbler.h @@ -0,0 +1,44 @@ +/* + * $Id: AudioScrobbler.h 238 2007-01-26 22:55:20Z stephen_booth $ + * + * Copyright (C) 2006 - 2007 Stephen F. Booth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#import + +#import + +@class PlaylistEntry; + +@interface AudioScrobbler : NSObject +{ + NSString *_pluginID; + NSMutableArray *_queue; + + BOOL _audioScrobblerThreadCompleted; + BOOL _keepProcessingAudioScrobblerCommands; + semaphore_t _semaphore; +} + +- (void) start:(PlaylistEntry *)pe; +- (void) stop; +- (void) pause; +- (void) resume; + +- (void) shutdown; + +@end diff --git a/AudioScrobbler/AudioScrobbler.m b/AudioScrobbler/AudioScrobbler.m new file mode 100644 index 000000000..5cbcc81a4 --- /dev/null +++ b/AudioScrobbler/AudioScrobbler.m @@ -0,0 +1,257 @@ +/* + * $Id: AudioScrobbler.m 238 2007-01-26 22:55:20Z stephen_booth $ + * + * Copyright (C) 2006 - 2007 Stephen F. Booth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#import "AudioScrobbler.h" + +#import "AudioScrobblerClient.h" +#import "PlaylistEntry.h" + +static NSString * +escapeForLastFM(NSString *string) +{ + NSMutableString *result = [string mutableCopy]; + + [result replaceOccurrencesOfString:@"&" + withString:@"&&" + options:NSLiteralSearch + range:NSMakeRange(0, [result length])]; + + return (nil == result ? @"" : [result autorelease]); +} + +@interface AudioScrobbler (Private) + +- (NSMutableArray *) queue; +- (NSString *) pluginID; + +- (void) sendCommand:(NSString *)command; + +- (BOOL) keepProcessingAudioScrobblerCommands; +- (void) setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands; + +- (BOOL) audioScrobblerThreadCompleted; +- (void) setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted; + +- (semaphore_t) semaphore; + +- (void) processAudioScrobblerCommands:(AudioScrobbler *)myself; + +@end + +@implementation AudioScrobbler + +- (id) init +{ + if((self = [super init])) { + + _pluginID = @"tst"; + + if([[NSUserDefaults standardUserDefaults] boolForKey:@"automaticallyLaunchLastFM"]) { + [[NSWorkspace sharedWorkspace] launchApplication:@"Last.fm.app"]; + } + + _keepProcessingAudioScrobblerCommands = YES; + + kern_return_t result = semaphore_create(mach_task_self(), &_semaphore, SYNC_POLICY_FIFO, 0); + + if(KERN_SUCCESS != result) { + NSLog(@"Couldn't create semaphore (%s).", mach_error_type(result)); + + [self release]; + return nil; + } + + [NSThread detachNewThreadSelector:@selector(processAudioScrobblerCommands:) toTarget:self withObject:self]; + } + return self; +} + +- (void) dealloc +{ + if([self keepProcessingAudioScrobblerCommands] || NO == [self audioScrobblerThreadCompleted]) { + [self shutdown]; + } + + [_queue release], _queue = nil; + + semaphore_destroy(mach_task_self(), _semaphore), _semaphore = 0; + + [super dealloc]; +} + +- (void) start:(PlaylistEntry *)pe +{ + [self sendCommand:[NSString stringWithFormat:@"START c=%@&a=%@&t=%@&b=%@&m=%@&l=%i&p=%@\n", + [self pluginID], + escapeForLastFM([pe artist]), + escapeForLastFM([pe title]), + escapeForLastFM([pe album]), + @"", // TODO: MusicBrainz support + (int)([pe length]/1000.0), + escapeForLastFM([pe filename]) + ]]; +} + +- (void) stop +{ + [self sendCommand:[NSString stringWithFormat:@"STOP c=%@\n", [self pluginID]]]; +} + +- (void) pause +{ + [self sendCommand:[NSString stringWithFormat:@"PAUSE c=%@\n", [self pluginID]]]; +} + +- (void) resume +{ + [self sendCommand:[NSString stringWithFormat:@"RESUME c=%@\n", [self pluginID]]]; +} + +- (void) shutdown +{ + [self setKeepProcessingAudioScrobblerCommands:NO]; + semaphore_signal([self semaphore]); + + // Wait for the thread to terminate + while(NO == [self audioScrobblerThreadCompleted]) { + [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; + } +} + +@end + +@implementation AudioScrobbler (Private) + +- (NSMutableArray *) queue +{ + if(nil == _queue) { + _queue = [[NSMutableArray alloc] init]; + } + + return _queue; +} + +- (NSString *) pluginID +{ + return _pluginID; +} + +- (void) sendCommand:(NSString *)command +{ + NSLog(@"Command: %@", command); + @synchronized([self queue]) { + [[self queue] addObject:command]; + } + semaphore_signal([self semaphore]); +} + +- (BOOL) keepProcessingAudioScrobblerCommands +{ + return _keepProcessingAudioScrobblerCommands; +} + +- (void) setKeepProcessingAudioScrobblerCommands:(BOOL)keepProcessingAudioScrobblerCommands +{ + _keepProcessingAudioScrobblerCommands = keepProcessingAudioScrobblerCommands; +} + +- (BOOL) audioScrobblerThreadCompleted +{ + return _audioScrobblerThreadCompleted; +} + +- (void) setAudioScrobblerThreadCompleted:(BOOL)audioScrobblerThreadCompleted +{ + _audioScrobblerThreadCompleted = audioScrobblerThreadCompleted; +} + +- (semaphore_t) semaphore +{ + return _semaphore; +} + +- (void) processAudioScrobblerCommands:(AudioScrobbler *)myself +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + AudioScrobblerClient *client = [[AudioScrobblerClient alloc] init]; + mach_timespec_t timeout = { 5, 0 }; + NSEnumerator *enumerator = nil; + NSString *command = nil; + NSString *response = nil; + in_port_t port = 33367; + + while([myself keepProcessingAudioScrobblerCommands]) { + + // Get the first command to be sent + @synchronized([myself queue]) { + + enumerator = [[myself queue] objectEnumerator]; + command = [enumerator nextObject]; + + [[myself queue] removeObjectIdenticalTo:command]; + } + + if(nil != command) { + @try { + port = [client connectToHost:@"localhost" port:port]; + [client send:command]; + + response = [client receive]; + if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)]) { + NSLog(@"AudioScrobbler error: %@", response); + } + + [client shutdown]; + } + + @catch(NSException *exception) { + [client shutdown]; +// NSLog(@"Exception: %@",exception); + continue; + } + } + + semaphore_timedwait([myself semaphore], timeout); + } + + // Send a final stop command to cleanup + @try { + port = [client connectToHost:@"localhost" port:port]; + [client send:[NSString stringWithFormat:@"STOP c=%@\n", [myself pluginID]]]; + + response = [client receive]; + if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)]) { + NSLog(@"AudioScrobbler error: %@", response); + } + + [client shutdown]; + } + + @catch(NSException *exception) { + [client shutdown]; + } + + [client release]; + [pool release]; + + [myself setAudioScrobblerThreadCompleted:YES]; +} + +@end diff --git a/AudioScrobbler/AudioScrobblerClient.h b/AudioScrobbler/AudioScrobblerClient.h new file mode 100644 index 000000000..7456d9644 --- /dev/null +++ b/AudioScrobbler/AudioScrobblerClient.h @@ -0,0 +1,39 @@ +/* + * $Id: AudioScrobblerClient.h 241 2007-01-26 23:02:09Z stephen_booth $ + * + * Copyright (C) 2006 - 2007 Stephen F. Booth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#import + +#include + +@interface AudioScrobblerClient : NSObject +{ + int _socket; + BOOL _doPortStepping; + in_port_t _lastPort; +} + +- (in_port_t) connectToHost:(NSString *)hostname port:(in_port_t)port; + +- (void) send:(NSString *)data; +- (NSString *) receive; + +- (void) shutdown; + +@end diff --git a/AudioScrobbler/AudioScrobblerClient.m b/AudioScrobbler/AudioScrobblerClient.m new file mode 100644 index 000000000..af6285880 --- /dev/null +++ b/AudioScrobbler/AudioScrobblerClient.m @@ -0,0 +1,189 @@ +/* + * $Id: AudioScrobblerClient.m 362 2007-02-13 05:30:49Z stephen_booth $ + * + * Copyright (C) 2006 - 2007 Stephen F. Booth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/* + * This is a port of the BlockingClient client class from + * the Last.fm ScrobSub library by sharevari + */ + +#import "AudioScrobblerClient.h" + +#define kBufferSize 1024 +#define kPortsToStep 5 + +static in_addr_t +addressForHost(NSString *hostname) +{ + NSCParameterAssert(nil != hostname); + + in_addr_t address; + struct hostent *hostinfo; + + address = inet_addr([hostname cStringUsingEncoding:NSASCIIStringEncoding]); + + if(INADDR_NONE == address) { + hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]); + if(NULL == hostinfo) { + NSLog(@"Unable to resolve address for \"%@\".", hostname); + return INADDR_NONE; + } + + address = *((in_addr_t *)hostinfo->h_addr_list[0]); + } + + return address; +} + +@interface AudioScrobblerClient (Private) +- (void) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port; +@end + +@implementation AudioScrobblerClient + +- (id) init +{ + if((self = [super init])) { + _socket = -1; + _lastPort = -1; + _doPortStepping = YES; + } + return self; +} + +- (in_port_t) connectToHost:(NSString *)hostname port:(in_port_t)port +{ + in_addr_t remoteAddress = addressForHost(hostname); + + [self connectToSocket:remoteAddress port:port]; + + return _lastPort; +} + +- (void) send:(NSString *)data +{ + const char *utf8data = [data UTF8String]; + unsigned len = strlen(utf8data); + unsigned bytesToSend = len; + unsigned totalBytesSent = 0; + ssize_t bytesSent = 0; + + while(totalBytesSent < bytesToSend && -1 != bytesSent) { + bytesSent = send(_socket, utf8data + totalBytesSent, bytesToSend - totalBytesSent, 0); + + if(-1 == bytesSent || 0 == bytesSent) { + NSLog(@"Unable to send data through socket"); + } + + totalBytesSent += bytesSent; + } +} + +- (NSString *) receive +{ + char buffer [ kBufferSize ]; + int readSize = kBufferSize - 1; + ssize_t bytesRead = 0; + BOOL keepGoing = YES; + NSString *result = nil; + + do { + bytesRead = recv(_socket, buffer, readSize, 0); + NSAssert1(-1 != bytesRead && 0 < bytesRead, @"Unable to receive data through socket (%s).", strerror(errno)); + + if('\n' == buffer[bytesRead - 1]) { + --bytesRead; + keepGoing = NO; + } + + buffer[bytesRead] = '\0'; + result = [[NSString alloc] initWithUTF8String:buffer]; + + } while(keepGoing); + + return [result autorelease]; +} + +- (void) shutdown +{ + int result; + char buffer [ kBufferSize ]; + ssize_t bytesRead; + + if(-1 == _socket) { + return; + } + + result = shutdown(_socket, SHUT_WR); + NSAssert1(-1 != result, @"Socket shutdown failed (%s).", strerror(errno)); + + for(;;) { + bytesRead = recv(_socket, buffer, kBufferSize, 0); + + NSAssert1(-1 != bytesRead, @"Waiting for shutdown confirmation failed (%s).", strerror(errno)); + + if(0 != bytesRead) { + NSLog(@"Received unexpected bytes during shutdown: %@.", [[[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding] autorelease]); + } + else { + break; + } + } + + result = close(_socket); + NSAssert1(-1 != result, @"Couldn't close socket (%s).", strerror(errno)); + + _socket = -1; +} + +@end + +@implementation AudioScrobblerClient (Private) + +- (void) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port +{ + struct sockaddr_in socketAddress; + int result; + + _socket = socket(AF_INET, SOCK_STREAM, 0); + NSAssert1(-1 != _socket, @"Unable to create socket (%s).", strerror(errno)); + + _lastPort = port; + socketAddress.sin_family = AF_INET; + socketAddress.sin_addr.s_addr = remoteAddress; + socketAddress.sin_port = htons(_lastPort); + + result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in)); + + if(_doPortStepping) { + while(-1 == result && _lastPort <= (port + kPortsToStep)) { + socketAddress.sin_port = htons(++_lastPort); + result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in)); + } + } + + if(-1 == result) { + _doPortStepping = NO; + close(_socket); + _socket = -1; + NSAssert1(-1 != result, @"Couldn't connect to server (%s).", strerror(errno)); + } +} + +@end diff --git a/Cog.xcodeproj/project.pbxproj b/Cog.xcodeproj/project.pbxproj index 7607d3805..2836d9da8 100644 --- a/Cog.xcodeproj/project.pbxproj +++ b/Cog.xcodeproj/project.pbxproj @@ -9,6 +9,10 @@ /* Begin PBXBuildFile section */ 1705F1510B8BCB0C00C8B40D /* Help in Resources */ = {isa = PBXBuildFile; fileRef = 1705F1420B8BCB0C00C8B40D /* Help */; }; 171678C00AC8C39E00C28CF3 /* SmartFolderNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */; }; + 1766C6920B911DF1004A7AE4 /* AudioScrobbler.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */; }; + 1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */ = {isa = PBXBuildFile; fileRef = 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */; }; + 1766C6940B911DF1004A7AE4 /* AudioScrobblerClient.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */; }; + 1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */ = {isa = PBXBuildFile; fileRef = 1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */; }; 1770429C0B8BC53600B86321 /* AppController.m in Sources */ = {isa = PBXBuildFile; fileRef = 177042980B8BC53600B86321 /* AppController.m */; }; 1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1770429A0B8BC53600B86321 /* PlaybackController.m */; }; 177EBF9E0B8BC2A70000BC8C /* AMRemovableColumnsTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 177EBF7A0B8BC2A70000BC8C /* AMRemovableColumnsTableView.m */; }; @@ -139,6 +143,8 @@ files = ( 17B61B630B90A28100BC003F /* CogAudio.framework in CopyFiles */, 17F94CCD0B8D090800A34E87 /* Sparkle.framework in CopyFiles */, + 1766C6920B911DF1004A7AE4 /* AudioScrobbler.h in CopyFiles */, + 1766C6940B911DF1004A7AE4 /* AudioScrobblerClient.h in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -150,6 +156,10 @@ 1705F1420B8BCB0C00C8B40D /* Help */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Help; sourceTree = ""; }; 171678BD0AC8C39E00C28CF3 /* SmartFolderNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SmartFolderNode.h; sourceTree = ""; }; 171678BE0AC8C39E00C28CF3 /* SmartFolderNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SmartFolderNode.m; sourceTree = ""; }; + 1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobbler.h; sourceTree = ""; }; + 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobbler.m; sourceTree = ""; }; + 1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AudioScrobblerClient.h; sourceTree = ""; }; + 1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AudioScrobblerClient.m; sourceTree = ""; }; 1770424E0B8BC41800B86321 /* Cog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Cog.app; sourceTree = BUILT_PRODUCTS_DIR; }; 177042970B8BC53600B86321 /* AppController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = AppController.h; sourceTree = ""; }; 177042980B8BC53600B86321 /* AppController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = AppController.m; sourceTree = ""; }; @@ -310,6 +320,7 @@ isa = PBXGroup; children = ( 177042960B8BC53600B86321 /* Application */, + 1766C68D0B911DF1004A7AE4 /* AudioScrobbler */, 8E07AAEA0AAC90DC00A4B32F /* Preferences */, 8EFFCD410AA093AF00C458A5 /* FileDrawer */, 8E75752309F31D5A0080F1EE /* Feedback */, @@ -345,6 +356,17 @@ name = "Other Frameworks"; sourceTree = ""; }; + 1766C68D0B911DF1004A7AE4 /* AudioScrobbler */ = { + isa = PBXGroup; + children = ( + 1766C68E0B911DF1004A7AE4 /* AudioScrobbler.h */, + 1766C68F0B911DF1004A7AE4 /* AudioScrobbler.m */, + 1766C6900B911DF1004A7AE4 /* AudioScrobblerClient.h */, + 1766C6910B911DF1004A7AE4 /* AudioScrobblerClient.m */, + ); + path = AudioScrobbler; + sourceTree = ""; + }; 177042960B8BC53600B86321 /* Application */ = { isa = PBXGroup; children = ( @@ -802,6 +824,8 @@ 1770429C0B8BC53600B86321 /* AppController.m in Sources */, 1770429E0B8BC53600B86321 /* PlaybackController.m in Sources */, 17D21DF60B8BE86900D1EBDE /* CoreAudioUtils.m in Sources */, + 1766C6930B911DF1004A7AE4 /* AudioScrobbler.m in Sources */, + 1766C6950B911DF1004A7AE4 /* AudioScrobblerClient.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Preferences/General/English.lproj/Preferences.nib/info.nib b/Preferences/General/English.lproj/Preferences.nib/info.nib index 62aa141ab..47f396a63 100644 --- a/Preferences/General/English.lproj/Preferences.nib/info.nib +++ b/Preferences/General/English.lproj/Preferences.nib/info.nib @@ -9,25 +9,25 @@ 10 587 659 506 102 0 0 1680 1028 11 - 703 634 273 151 0 0 1680 1028 + 699 367 273 151 0 0 1680 1028 43 - 516 528 337 116 0 0 1680 1028 + 383 476 337 116 0 0 1680 1028 50 - 662 662 355 96 0 0 1680 1028 + 769 536 355 96 0 0 1680 1028 58 - 634 659 411 101 0 0 1680 1028 + 385 271 411 101 0 0 1680 1028 85 - 614 652 452 116 0 0 1680 1028 + 596 829 452 116 0 0 1680 1028 IBFramework Version 446.1 IBOpenObjects 10 - 58 85 43 50 + 58 11 IBSystem Version diff --git a/Preferences/General/English.lproj/Preferences.nib/keyedobjects.nib b/Preferences/General/English.lproj/Preferences.nib/keyedobjects.nib index 347df1791..f68588313 100644 Binary files a/Preferences/General/English.lproj/Preferences.nib/keyedobjects.nib and b/Preferences/General/English.lproj/Preferences.nib/keyedobjects.nib differ