Permalink
Browse files

Use delegates and thus be more beautiful

Also fixes a bunch of bugs. Sorry about the lack of atomicity.
  • Loading branch information...
1 parent f82e1e8 commit 05bba002b994c357c481a971dfd662db5e158608 @mxcl committed Jul 15, 2010
Showing with 226 additions and 258 deletions.
  1. +1 −2 Audioscrobbler.xcodeproj/project.pbxproj
  2. +19 −2 ITunesListener.h
  3. +65 −138 ITunesListener.m
  4. +2 −1 MainController.h
  5. +125 −86 MainController.m
  6. +1 −1 NSDictionary+Track.h
  7. +7 −15 NSDictionary+Track.m
  8. +1 −0 lastfm.m
  9. +5 −13 pc.h
@@ -44,7 +44,6 @@
63D1D2990F9A0CB1004FBC05 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63D1D2980F9A0CB1004FBC05 /* Carbon.framework */; };
63D6DAF10F8396E000F4DCAA /* MainController.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D6DAF00F8396E000F4DCAA /* MainController.m */; };
63D6DB380F8399E000F4DCAA /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 63D6DB360F8399E000F4DCAA /* icon.png */; };
- 63D6DB390F8399E000F4DCAA /* inverted_icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 63D6DB370F8399E000F4DCAA /* inverted_icon.png */; };
63F19A1B11D67ED700205103 /* HighResolutionTimer.h in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 63F19A1911D67ED700205103 /* HighResolutionTimer.h */; };
63F19A1C11D67ED700205103 /* HighResolutionTimer.m in Sources */ = {isa = PBXBuildFile; fileRef = 63F19A1A11D67ED700205103 /* HighResolutionTimer.m */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
@@ -196,14 +195,14 @@
children = (
637F07050FAF180F006EE129 /* iTunes.h */,
29B97316FDCFA39411CA2CEA /* main.m */,
+ 32CA4F630368D1EE00C91783 /* pc.h */,
);
name = "Other Sources";
sourceTree = "<group>";
};
29B97317FDCFA39411CA2CEA /* Resources */ = {
isa = PBXGroup;
children = (
- 32CA4F630368D1EE00C91783 /* pc.h */,
63D6DB360F8399E000F4DCAA /* icon.png */,
63A29EAA11D6B90500D2441D /* icon_inverted.png */,
63A29EAE11D6B92100D2441D /* icon_green.png */,
View
@@ -23,17 +23,34 @@
@class Lastfm;
@class HighResolutionTimer;
+
+@protocol ITunesDelegate <NSObject>
+@optional
+-(void)iTunesTrackStarted:(NSDictionary*)track art:(NSData*)art;
+-(void)iTunesTrackPaused:(NSDictionary*)track;
+-(void)iTunesTrackResumed:(NSDictionary*)track art:(NSData*)art;
+-(void)iTunesPlaybackStopped;
+
+-(void)iTunesTrackWasRatedFourStarsOrAbove:(NSDictionary*)track;
+-(void)iTunesWontScrobble:(NSDictionary*)track because:(NSString*)reason;
+-(void)iTunesTrackMetadataUpdated:(NSDictionary*)track;
+@end
+
+
@interface ITunesListener : NSObject {
NSMutableDictionary* track;
+ NSData* art;
time_t start_time;
char state;
ITunesApplication* itunes;
Lastfm* lastfm;
HighResolutionTimer* timer;
+ id <ITunesDelegate> delegate;
}
--(id)initWithLastfm:(Lastfm*)lastfm;
--(NSDictionary*)track;
+@property(readonly) NSDictionary* track;
+
+-(id)initWithLastfm:(Lastfm*)lastfm delegate:(id)delegate;
-(void)onPlayerInfo:(NSNotification*)userData;
@end
View
@@ -23,7 +23,6 @@
#import "iTunes.h"
#import "lastfm.h"
#import "NSDictionary+Track.h"
-#import <Growl/GrowlApplicationBridge.h>
#import <time.h>
static time_t now()
@@ -35,12 +34,29 @@ static time_t now()
}
+// Can't make this a category sadly, due to ScriptingFramework oddness
+static NSData* itunes_current_track_artwork_as_data(ITunesApplication* itunes)
+{
+ @try {
+ ITunesArtwork* iart = [itunes.currentTrack.artworks objectAtIndex:0];
+ // NSImage is more useful, but Growl needs a TIFF and this way we
+ // save on allocations, thus keeping our memory footprint down
+ return [iart.data.TIFFRepresentation retain];
+ }
+ @catch(id e) {
+ // seems to throw sometimes even if we check for the right stuff first
+ return nil;
+ }
+}
+
+
@implementation ITunesListener
--(id)initWithLastfm:(Lastfm*)lfm
+-(id)initWithLastfm:(Lastfm*)lfm delegate:(id)helegate
{
+ delegate = helegate;
lastfm = [lfm retain];
- state = STATE_STOPPED;
+ state = StateStopped;
start_time = 0;
itunes = [[SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"] retain];
timer = [[HighResolutionTimer alloc] initWithTarget:self action:@selector(submit)];
@@ -77,99 +93,16 @@ -(void)dealloc
[timer release];
[itunes release];
[lastfm release];
+ [track release];
+ [art release];
[super dealloc];
}
-
--(bool)transitionInvalid:(uint)transition
-{
- switch (state) {
- case STATE_STOPPED:
- switch (transition) {
- case TrackStarted:
- return false;
- case PlaybackStopped:
- case TrackPaused:
- case TrackMetadataChanged:
- case TrackResumed:
- return true;
- }
- case STATE_PAUSED:
- switch (transition) {
- case PlaybackStopped:
- case TrackMetadataChanged:
- case TrackResumed:
- case TrackStarted:
- return false;
- case TrackPaused:
- return true;
- }
- case STATE_PLAYING:
- switch (transition) {
- case PlaybackStopped:
- case TrackPaused:
- case TrackMetadataChanged:
- case TrackStarted:
- return false;
- case TrackResumed:
- return true;
- }
- }
- return true;
-}
-
--(void)announce:(uint)transition
-{
- // TODO should apply to everything, no? Not just the UI announcement.
- if ([self transitionInvalid:transition])
- return;
-
- NSMutableDictionary* dict = [track mutableCopy];
- [dict setObject:[NSNumber numberWithUnsignedInt:transition] forKey:@"Transition"];
-
- NSNotification*notification = [NSNotification notificationWithName:@"playerInfo"
- object:self
- userInfo:dict];
- [[NSNotificationQueue defaultQueue] enqueueNotification:notification
- postingStyle:NSPostNow
- coalesceMask:NSNotificationCoalescingOnName
- forModes:nil];
- [dict release];
-}
-
-(void)submit
{
[lastfm scrobble:track startTime:start_time];
}
--(void)start
-{
- state = STATE_PLAYING;
- start_time = now();
-
- [timer scheduleWithTimeout:[Lastfm scrobblePointForTrackWithDurationInSeconds:track.duration]];
-
- // we wait a second so that we don't spam Last.fm and so that stuff like
- // Growl (for auth) doesn't fill the screen when you skip-skip-skip
- [NSObject cancelPreviousPerformRequestsWithTarget:lastfm];
- [lastfm performSelector:@selector(updateNowPlaying:) withObject:track afterDelay:2.0];
-
- [self announce:TrackStarted];
-}
-
--(void)load_album_art
-{
- @try {
- ITunesArtwork* art = (ITunesArtwork*)[itunes.currentTrack.artworks objectAtIndex:0];
- // NSImage is more useful, but Growl needs a TIFF and this way we
- // save on allocations, thus keeping our memory footprint down
- [track setObject:[[art data] TIFFRepresentation] forKey:@"Album Art"];
- }
- @catch(id e) {
- // for some reason [art exists] returns true, but it will still throw!
- }
-}
-
-(void)amendMetadataIfAppropriate:(NSDictionary*)dict
{
#define NOTEQUAL(x) ![[dict objectForKey:x] isEqualToString:[track objectForKey:x]]
@@ -178,95 +111,89 @@ -(void)amendMetadataIfAppropriate:(NSDictionary*)dict
track.artist = dict.artist;
track.title = dict.title;
track.album = dict.album;
- [self announce:TrackMetadataChanged];
+ [delegate iTunesTrackMetadataUpdated:track];
}
#undef EQUAL
}
-static void would_play_again_growl(NSDictionary* d)
-{
- NSMutableDictionary* dict = [[d mutableCopy] autorelease];
- [dict setObject:ASGrowlLoveTrackQuery forKey:@"Notification Name"];
-
- [GrowlApplicationBridge notifyWithTitle:@"A+++++ Would Play Again!"
- description:@"Click this notification to love this track at Last.fm"
- notificationName:ASGrowlLoveTrackQuery
- iconData:nil
- priority:0
- isSticky:false
- clickContext:dict];
-}
-
-static void ignore_growl(NSString* title, NSString* reason)
-{
- [GrowlApplicationBridge notifyWithTitle:@"Will Not Scrobble"
- description:[NSString stringWithFormat:@"%@” is %@.", title, reason]
- notificationName:ASGrowlTrackIgnored
- iconData:nil
- priority:0
- isSticky:false
- clickContext:nil];
-}
-
-(void)onPlayerInfo:(NSNotification*)note
{
NSDictionary* newtrack = note.userInfo;
switch (newtrack.playerState) {
- case STATE_PLAYING:
+ case StatePlaying:
if (itunes.currentTrack.podcast) {
- ignore_growl(newtrack.title, @"a podcast");
+ [delegate iTunesWontScrobble:newtrack because:@"a podcast"];
goto stop;
}
if (![itunes.currentTrack.kind hasSuffix:@"audio file"]) {
- ignore_growl(newtrack.title, @"not music");
+ [delegate iTunesWontScrobble:newtrack because:@"not music"];
goto stop;
}
if (track.pid != newtrack.pid) {
[track release];
+ [art release];
track = [newtrack mutableCopy];
- [self load_album_art];
- [self start];
+ art = itunes_current_track_artwork_as_data(itunes);
+ goto start;
}
- else if (state == STATE_PAUSED) {
- state = STATE_PLAYING;
+ else if (state == StatePaused) {
+ state = StatePlaying;
[timer resume];
- [self announce:TrackResumed];
+ [delegate iTunesTrackResumed:track art:art];
}
- else if ([track isEqualToTrack:newtrack]) {
+ else if ([track isEqualToDictionary:newtrack]) {
// user restarted the track that was already playing, probably
- [self start];
+ goto start;
}
else {
- if (track.unrated && newtrack.rating >= 80)
- would_play_again_growl(newtrack);
-
[self amendMetadataIfAppropriate:newtrack];
+
+ if (track.unrated && newtrack.rating >= 80) {
+ track.rating = newtrack.rating;
+ [delegate iTunesTrackWasRatedFourStarsOrAbove:track];
+ }
}
break;
-
- case STATE_PAUSED:
- state = STATE_PAUSED;
+
+ start:
+ state = StatePlaying;
+ start_time = now();
+
+ [timer scheduleWithTimeout:[Lastfm scrobblePointForTrackWithDurationInSeconds:track.duration]];
+
+ // we wait a bit so that we don't spam Last.fm when you skip-skip-skip
+ [NSObject cancelPreviousPerformRequestsWithTarget:lastfm];
+ [lastfm performSelector:@selector(updateNowPlaying:) withObject:track afterDelay:2.0];
+
+ [delegate iTunesTrackStarted:track art:art];
+ break;
+
+ case StatePaused:
+ if (state == StatePaused || state == StateStopped)
+ break;
+ state = StatePaused;
[timer pause];
- [self announce:TrackPaused];
+ [delegate iTunesTrackPaused:track];
break;
- case STATE_STOPPED:
+ case StateStopped:
stop:
+ if (state == StateStopped)
+ break;
+ state = StateStopped;
[timer stop];
- state = STATE_STOPPED;
[track release];
+ [art release];
track = nil;
- [self announce:PlaybackStopped];
+ art = nil;
+ [delegate iTunesPlaybackStopped];
break;
}
}
--(NSDictionary*)track
-{
- return track;
-}
+@synthesize track;
@end
View
@@ -21,11 +21,12 @@
#import <Growl/GrowlApplicationBridge.h>
#import <Cocoa/Cocoa.h>
#import "lastfm.h"
+#import "ITunesListener.h"
@class AutoDash;
@class ShareWindowController;
-@interface MainController : NSObject <GrowlApplicationBridgeDelegate, LastfmDelegate>
+@interface MainController : NSObject <GrowlApplicationBridgeDelegate, LastfmDelegate, ITunesDelegate>
{
NSStatusItem* status_item;
IBOutlet NSMenu* menu;
Oops, something went wrong.

0 comments on commit 05bba00

Please sign in to comment.