Updated TODO.

Seeded shuffle.
Updated AudioScrobbler code from Play..
CQTexperiment
vspader 2007-07-05 23:24:25 +00:00
parent a2b9db5f58
commit a04f78e7d8
5 changed files with 125 additions and 78 deletions

View File

@ -23,6 +23,14 @@
#import "AudioScrobblerClient.h" #import "AudioScrobblerClient.h"
#import "PlaylistEntry.h" #import "PlaylistEntry.h"
// ========================================
// Symbolic Constants
// ========================================
NSString * const AudioScrobblerRunLoopMode = @"org.sbooth.Play.AudioScrobbler.RunLoopMode";
// ========================================
// Helpers
// ========================================
static NSString * static NSString *
escapeForLastFM(NSString *string) escapeForLastFM(NSString *string)
{ {
@ -61,11 +69,10 @@ escapeForLastFM(NSString *string)
{ {
if((self = [super init])) { if((self = [super init])) {
_pluginID = @"cog"; _pluginID = @"pla";
if([[NSUserDefaults standardUserDefaults] boolForKey:@"automaticallyLaunchLastFM"]) { if([[NSUserDefaults standardUserDefaults] boolForKey:@"automaticallyLaunchLastFM"])
[[NSWorkspace sharedWorkspace] launchApplication:@"Last.fm.app"]; [[NSWorkspace sharedWorkspace] launchApplication:@"Last.fm.app"];
}
_keepProcessingAudioScrobblerCommands = YES; _keepProcessingAudioScrobblerCommands = YES;
@ -85,9 +92,8 @@ escapeForLastFM(NSString *string)
- (void) dealloc - (void) dealloc
{ {
if([self keepProcessingAudioScrobblerCommands] || NO == [self audioScrobblerThreadCompleted]) { if([self keepProcessingAudioScrobblerCommands] || NO == [self audioScrobblerThreadCompleted])
[self shutdown]; [self shutdown];
}
[_queue release], _queue = nil; [_queue release], _queue = nil;
@ -98,15 +104,15 @@ escapeForLastFM(NSString *string)
- (void) start:(PlaylistEntry *)pe - (void) start:(PlaylistEntry *)pe
{ {
[self sendCommand:[NSString stringWithFormat:@"START c=%@&a=%@&t=%@&b=%@&m=%@&l=%i&p=%@\n", [self sendCommand:[NSString stringWithFormat:@"START c=%@&a=%@&t=%@&b=%@&m=%@&l=%i&p=%@\n",
[self pluginID], [self pluginID],
escapeForLastFM([pe artist]), escapeForLastFM([pe artist]),
escapeForLastFM([pe title]), escapeForLastFM([pe title]),
escapeForLastFM([pe album]), escapeForLastFM([pe album]),
@"", // TODO: MusicBrainz support @"", // TODO: MusicBrainz support
(int)([[pe length] doubleValue]/1000.0), (int)([[pe length] doubleValue]/1000.0),
escapeForLastFM([[pe url] path]) escapeForLastFM([[pe url] path])
]]; ]];
} }
- (void) stop - (void) stop
@ -130,9 +136,8 @@ escapeForLastFM(NSString *string)
semaphore_signal([self semaphore]); semaphore_signal([self semaphore]);
// Wait for the thread to terminate // Wait for the thread to terminate
while(NO == [self audioScrobblerThreadCompleted]) { while(NO == [self audioScrobblerThreadCompleted])
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.5]]; [[NSRunLoop currentRunLoop] runMode:AudioScrobblerRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.2]];
}
} }
@end @end
@ -141,9 +146,8 @@ escapeForLastFM(NSString *string)
- (NSMutableArray *) queue - (NSMutableArray *) queue
{ {
if(nil == _queue) { if(nil == _queue)
_queue = [[NSMutableArray alloc] init]; _queue = [[NSMutableArray alloc] init];
}
return _queue; return _queue;
} }
@ -155,7 +159,6 @@ escapeForLastFM(NSString *string)
- (void) sendCommand:(NSString *)command - (void) sendCommand:(NSString *)command
{ {
NSLog(@"Command: %@", command);
@synchronized([self queue]) { @synchronized([self queue]) {
[[self queue] addObject:command]; [[self queue] addObject:command];
} }
@ -201,7 +204,6 @@ escapeForLastFM(NSString *string)
// Get the first command to be sent // Get the first command to be sent
@synchronized([myself queue]) { @synchronized([myself queue]) {
enumerator = [[myself queue] objectEnumerator]; enumerator = [[myself queue] objectEnumerator];
command = [[enumerator nextObject] retain]; command = [[enumerator nextObject] retain];
@ -210,17 +212,17 @@ escapeForLastFM(NSString *string)
if(nil != command) { if(nil != command) {
@try { @try {
port = [client connectToHost:@"localhost" port:port]; if([client connectToHost:@"localhost" port:port]) {
[client send:command]; port = [client connectedPort];
[client send:command];
[command release]; [command release];
response = [client receive]; response = [client receive];
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)]) { if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)])
NSLog(@"AudioScrobbler error: %@", response); NSLog(@"AudioScrobbler error: %@", response);
[client shutdown];
} }
[client shutdown];
} }
@catch(NSException *exception) { @catch(NSException *exception) {
@ -235,15 +237,15 @@ escapeForLastFM(NSString *string)
// Send a final stop command to cleanup // Send a final stop command to cleanup
@try { @try {
port = [client connectToHost:@"localhost" port:port]; if([client connectToHost:@"localhost" port:port]) {
[client send:[NSString stringWithFormat:@"STOP c=%@\n", [myself pluginID]]]; [client send:[NSString stringWithFormat:@"STOP c=%@\n", [myself pluginID]]];
response = [client receive]; response = [client receive];
if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)]) { if(2 > [response length] || NSOrderedSame != [response compare:@"OK" options:NSLiteralSearch range:NSMakeRange(0,2)])
NSLog(@"AudioScrobbler error: %@", response); NSLog(@"AudioScrobbler error: %@", response);
[client shutdown];
} }
[client shutdown];
} }
@catch(NSException *exception) { @catch(NSException *exception) {
@ -251,9 +253,9 @@ escapeForLastFM(NSString *string)
} }
[client release]; [client release];
[pool release];
[myself setAudioScrobblerThreadCompleted:YES]; [myself setAudioScrobblerThreadCompleted:YES];
[pool release];
} }
@end @end

View File

@ -1,5 +1,5 @@
/* /*
* $Id: AudioScrobblerClient.h 241 2007-01-26 23:02:09Z stephen_booth $ * $Id: AudioScrobblerClient.h 666 2007-04-26 16:35:18Z stephen_booth $
* *
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org> * Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
* *
@ -26,10 +26,13 @@
{ {
int _socket; int _socket;
BOOL _doPortStepping; BOOL _doPortStepping;
in_port_t _lastPort; in_port_t _port;
} }
- (in_port_t) connectToHost:(NSString *)hostname port:(in_port_t)port; - (BOOL) connectToHost:(NSString *)hostname port:(in_port_t)port;
- (BOOL) isConnected;
- (in_port_t) connectedPort;
- (void) send:(NSString *)data; - (void) send:(NSString *)data;
- (NSString *) receive; - (NSString *) receive;

View File

@ -1,5 +1,5 @@
/* /*
* $Id: AudioScrobblerClient.m 362 2007-02-13 05:30:49Z stephen_booth $ * $Id: AudioScrobblerClient.m 869 2007-06-18 15:51:33Z stephen_booth $
* *
* Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org> * Copyright (C) 2006 - 2007 Stephen F. Booth <me@sbooth.org>
* *
@ -41,7 +41,7 @@ addressForHost(NSString *hostname)
if(INADDR_NONE == address) { if(INADDR_NONE == address) {
hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]); hostinfo = gethostbyname([hostname cStringUsingEncoding:NSASCIIStringEncoding]);
if(NULL == hostinfo) { if(NULL == hostinfo) {
NSLog(@"Unable to resolve address for \"%@\".", hostname); NSLog(@"AudioScrobblerClient error: Unable to resolve address for \"%@\".", hostname);
return INADDR_NONE; return INADDR_NONE;
} }
@ -52,7 +52,7 @@ addressForHost(NSString *hostname)
} }
@interface AudioScrobblerClient (Private) @interface AudioScrobblerClient (Private)
- (void) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port; - (BOOL) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port;
@end @end
@implementation AudioScrobblerClient @implementation AudioScrobblerClient
@ -61,35 +61,51 @@ addressForHost(NSString *hostname)
{ {
if((self = [super init])) { if((self = [super init])) {
_socket = -1; _socket = -1;
_lastPort = -1;
_doPortStepping = YES; _doPortStepping = YES;
} }
return self; return self;
} }
- (in_port_t) connectToHost:(NSString *)hostname port:(in_port_t)port - (BOOL) connectToHost:(NSString *)hostname port:(in_port_t)port
{ {
in_addr_t remoteAddress = addressForHost(hostname); NSParameterAssert(nil != hostname);
[self connectToSocket:remoteAddress port:port]; in_addr_t remoteAddress = addressForHost(hostname);
return _lastPort; if(INADDR_NONE != remoteAddress)
return [self connectToSocket:remoteAddress port:port];
return NO;
}
- (BOOL) isConnected
{
return (-1 != _socket);
}
- (in_port_t) connectedPort
{
return _port;
} }
- (void) send:(NSString *)data - (void) send:(NSString *)data
{ {
const char *utf8data = [data UTF8String]; const char *utf8data = [data UTF8String];
unsigned len = strlen(utf8data); unsigned len = strlen(utf8data);
unsigned bytesToSend = len; unsigned bytesToSend = len;
unsigned totalBytesSent = 0; unsigned totalBytesSent = 0;
ssize_t bytesSent = 0; ssize_t bytesSent = 0;
if(NO == [self isConnected]) {
NSLog(@"AudioScrobblerClient error: Can't send data, client not connected");
return;
}
while(totalBytesSent < bytesToSend && -1 != bytesSent) { while(totalBytesSent < bytesToSend && -1 != bytesSent) {
bytesSent = send(_socket, utf8data + totalBytesSent, bytesToSend - totalBytesSent, 0); bytesSent = send(_socket, utf8data + totalBytesSent, bytesToSend - totalBytesSent, 0);
if(-1 == bytesSent || 0 == bytesSent) { if(-1 == bytesSent || 0 == bytesSent)
NSLog(@"Unable to send data through socket"); NSLog(@"AudioScrobblerClient error: Unable to send data through socket: %s", strerror(errno));
}
totalBytesSent += bytesSent; totalBytesSent += bytesSent;
} }
@ -102,10 +118,18 @@ addressForHost(NSString *hostname)
ssize_t bytesRead = 0; ssize_t bytesRead = 0;
BOOL keepGoing = YES; BOOL keepGoing = YES;
NSString *result = nil; NSString *result = nil;
if(NO == [self isConnected]) {
NSLog(@"AudioScrobblerClient error: Can't receive data, client not connected");
return nil;
}
do { do {
bytesRead = recv(_socket, buffer, readSize, 0); bytesRead = recv(_socket, buffer, readSize, 0);
NSAssert1(-1 != bytesRead && 0 < bytesRead, @"Unable to receive data through socket (%s).", strerror(errno)); if(-1 == bytesRead || 0 == bytesRead) {
NSLog(@"AudioScrobblerClient error: Unable to receive data through socket: %s", strerror(errno));
break;
}
if('\n' == buffer[bytesRead - 1]) { if('\n' == buffer[bytesRead - 1]) {
--bytesRead; --bytesRead;
@ -126,64 +150,80 @@ addressForHost(NSString *hostname)
char buffer [ kBufferSize ]; char buffer [ kBufferSize ];
ssize_t bytesRead; ssize_t bytesRead;
if(-1 == _socket) { if(NO == [self isConnected]) {
return; return;
} }
result = shutdown(_socket, SHUT_WR); result = shutdown(_socket, SHUT_WR);
NSAssert1(-1 != result, @"Socket shutdown failed (%s).", strerror(errno)); if(-1 == result)
NSLog(@"AudioScrobblerClient error: Socket shutdown failed: %s", strerror(errno));
for(;;) { for(;;) {
bytesRead = recv(_socket, buffer, kBufferSize, 0); bytesRead = recv(_socket, buffer, kBufferSize, 0);
if(-1 == bytesRead)
NSAssert1(-1 != bytesRead, @"Waiting for shutdown confirmation failed (%s).", strerror(errno)); NSLog(@"AudioScrobblerClient error: Waiting for shutdown confirmation failed: %s", strerror(errno));
if(0 != bytesRead) { if(0 != bytesRead) {
NSLog(@"Received unexpected bytes during shutdown: %@.", [[[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding] autorelease]); NSString *received = [[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding];
NSLog(@"Received unexpected bytes during shutdown: %@", received);
[received release];
} }
else { else
break; break;
}
} }
result = close(_socket); result = close(_socket);
NSAssert1(-1 != result, @"Couldn't close socket (%s).", strerror(errno)); if(-1 == result)
NSLog(@"Couldn't close socket (%s)", strerror(errno));
_socket = -1; _socket = -1;
_port = 0;
} }
@end @end
@implementation AudioScrobblerClient (Private) @implementation AudioScrobblerClient (Private)
- (void) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port - (BOOL) connectToSocket:(in_addr_t)remoteAddress port:(in_port_t)port
{ {
NSParameterAssert(INADDR_NONE != remoteAddress);
struct sockaddr_in socketAddress; struct sockaddr_in socketAddress;
int result; int result;
_socket = socket(AF_INET, SOCK_STREAM, 0); _socket = socket(AF_INET, SOCK_STREAM, 0);
NSAssert1(-1 != _socket, @"Unable to create socket (%s).", strerror(errno)); if(-1 == _socket) {
NSLog(@"Unable to create socket (%s)", strerror(errno));
return NO;
}
_lastPort = port; _port = port;
socketAddress.sin_family = AF_INET; socketAddress.sin_family = AF_INET;
socketAddress.sin_addr.s_addr = remoteAddress; socketAddress.sin_addr.s_addr = remoteAddress;
socketAddress.sin_port = htons(_lastPort); socketAddress.sin_port = htons(_port);
result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in)); result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in));
// Don't check result yet
if(_doPortStepping) { if(_doPortStepping) {
while(-1 == result && _lastPort <= (port + kPortsToStep)) { while(-1 == result && _port <= (port + kPortsToStep)) {
socketAddress.sin_port = htons(++_lastPort); socketAddress.sin_port = htons(++_port);
result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in)); result = connect(_socket, (const struct sockaddr *)&socketAddress, sizeof(struct sockaddr_in));
} }
} }
// Don't log failures, because the client may not be running
if(-1 == result) { if(-1 == result) {
_doPortStepping = NO;
close(_socket); close(_socket);
_socket = -1;
// NSAssert1(-1 != result, @"Couldn't connect to server (%s).", strerror(errno)); _socket = -1;
_port = 0;
_doPortStepping = NO;
return NO;
} }
return YES;
} }
@end @end

View File

@ -28,6 +28,8 @@ int randint(int low, int high)
+ (NSMutableArray *)shuffleList:(NSArray *)l + (NSMutableArray *)shuffleList:(NSArray *)l
{ {
srandom(time(NULL));
NSMutableArray *a = [l mutableCopy]; NSMutableArray *a = [l mutableCopy];
NSMutableArray *b = [[NSMutableArray alloc] init]; NSMutableArray *b = [[NSMutableArray alloc] init];

6
TODO
View File

@ -5,8 +5,8 @@ Incubate.
BUGS: BUGS:
Files still in use (File handle leak?): http://sbooth.org/forums/viewtopic.php?t=1619 Files still in use (File handle leak?): http://sbooth.org/forums/viewtopic.php?t=1619
Window order not preserved (incorrect use of makeKeyAndOrderFront in nsapp dock click delegate method?): http://sbooth.org/forums/viewtopic.php?t=1615 #Window order not preserved (incorrect use of makeKeyAndOrderFront in nsapp dock click delegate method?): http://sbooth.org/forums/viewtopic.php?t=1615
Slider enabled on launch: http://sbooth.org/forums/viewtopic.php?t=1471 #Slider enabled on launch: http://sbooth.org/forums/viewtopic.php?t=1471
Drag slider to the end: http://sbooth.org/forums/viewtopic.php?t=1537 #Drag slider to the end: http://sbooth.org/forums/viewtopic.php?t=1537
Shuffle is not random: http://sbooth.org/forums/viewtopic.php?p=5225 Shuffle is not random: http://sbooth.org/forums/viewtopic.php?p=5225