Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Explicit scrobbling errors

Also refactored some stuff.
  • Loading branch information...
commit ce99f3aab49c7da1a5961b2ff4bf2f2d0e350067 1 parent 6d779ff
Max Howell authored
24 Audioscrobbler.xcodeproj/project.pbxproj
View
@@ -31,15 +31,15 @@
63451BC711BAD1C50087E3AB /* NSDictionary+Track.m in Sources */ = {isa = PBXBuildFile; fileRef = 63451BC611BAD1C50087E3AB /* NSDictionary+Track.m */; };
6372C84F1007A01B00EC25F4 /* methylblue.pem in Resources */ = {isa = PBXBuildFile; fileRef = 6372C84E1007A01B00EC25F4 /* methylblue.pem */; };
637F074E0FAF20AD006EE129 /* ScriptingBridge.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 637F074D0FAF20AD006EE129 /* ScriptingBridge.framework */; };
+ 6390167511CCE4DA00DCCC84 /* ShareWindowController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6390167411CCE4DA00DCCC84 /* ShareWindowController.m */; };
63906F0C0F9DE71E00797A14 /* ShareWindow.xib in Resources */ = {isa = PBXBuildFile; fileRef = 63906F0B0F9DE71E00797A14 /* ShareWindow.xib */; };
63C9B94A0F8672940065ED7F /* Audioscrobbler.icns in Resources */ = {isa = PBXBuildFile; fileRef = 63C9B9490F8672940065ED7F /* Audioscrobbler.icns */; };
63C9B9630F867C460065ED7F /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63C9B9620F867C460065ED7F /* Security.framework */; };
63D1D09F0F97FDB3004FBC05 /* Growl.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63D1D09E0F97FDB3004FBC05 /* Growl.framework */; };
63D1D0C40F97FF9B004FBC05 /* Growl.framework in Copy Frameworks */ = {isa = PBXBuildFile; fileRef = 63D1D09E0F97FDB3004FBC05 /* Growl.framework */; };
63D1D1040F980532004FBC05 /* lastfm.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D1D1030F980532004FBC05 /* lastfm.m */; };
- 63D1D1A20F989847004FBC05 /* HistoryMenuController.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D1D1A10F989847004FBC05 /* HistoryMenuController.m */; };
63D1D2990F9A0CB1004FBC05 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 63D1D2980F9A0CB1004FBC05 /* Carbon.framework */; };
- 63D6DAF10F8396E000F4DCAA /* StatusItemController.m in Sources */ = {isa = PBXBuildFile; fileRef = 63D6DAF00F8396E000F4DCAA /* StatusItemController.m */; };
+ 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 */; };
8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; };
@@ -81,17 +81,17 @@
6372C84E1007A01B00EC25F4 /* methylblue.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = methylblue.pem; sourceTree = "<group>"; };
637F07050FAF180F006EE129 /* iTunes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = iTunes.h; sourceTree = "<group>"; };
637F074D0FAF20AD006EE129 /* ScriptingBridge.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ScriptingBridge.framework; path = /System/Library/Frameworks/ScriptingBridge.framework; sourceTree = "<absolute>"; };
+ 6390167311CCE4DA00DCCC84 /* ShareWindowController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ShareWindowController.h; sourceTree = "<group>"; };
+ 6390167411CCE4DA00DCCC84 /* ShareWindowController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ShareWindowController.m; sourceTree = "<group>"; };
63906F0B0F9DE71E00797A14 /* ShareWindow.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareWindow.xib; sourceTree = "<group>"; };
63C9B9490F8672940065ED7F /* Audioscrobbler.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = Audioscrobbler.icns; sourceTree = "<group>"; };
63C9B9620F867C460065ED7F /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = /System/Library/Frameworks/Security.framework; sourceTree = "<absolute>"; };
63D1D09E0F97FDB3004FBC05 /* Growl.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = Growl.framework; sourceTree = "<group>"; };
63D1D1020F980532004FBC05 /* lastfm.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = lastfm.h; sourceTree = "<group>"; };
63D1D1030F980532004FBC05 /* lastfm.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = lastfm.m; sourceTree = "<group>"; };
- 63D1D1A00F989847004FBC05 /* HistoryMenuController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HistoryMenuController.h; sourceTree = "<group>"; };
- 63D1D1A10F989847004FBC05 /* HistoryMenuController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HistoryMenuController.m; sourceTree = "<group>"; };
63D1D2980F9A0CB1004FBC05 /* Carbon.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Carbon.framework; path = /System/Library/Frameworks/Carbon.framework; sourceTree = "<absolute>"; };
- 63D6DAEF0F8396E000F4DCAA /* StatusItemController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusItemController.h; sourceTree = "<group>"; };
- 63D6DAF00F8396E000F4DCAA /* StatusItemController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusItemController.m; sourceTree = "<group>"; };
+ 63D6DAEF0F8396E000F4DCAA /* MainController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MainController.h; sourceTree = "<group>"; };
+ 63D6DAF00F8396E000F4DCAA /* MainController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MainController.m; sourceTree = "<group>"; };
63D6DB360F8399E000F4DCAA /* icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = icon.png; sourceTree = "<group>"; };
63D6DB370F8399E000F4DCAA /* inverted_icon.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = inverted_icon.png; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -119,18 +119,18 @@
080E96DDFE201D6D7F000001 /* Classes */ = {
isa = PBXGroup;
children = (
- 63D6DAEF0F8396E000F4DCAA /* StatusItemController.h */,
- 63D6DAF00F8396E000F4DCAA /* StatusItemController.m */,
+ 63D6DAEF0F8396E000F4DCAA /* MainController.h */,
+ 63D6DAF00F8396E000F4DCAA /* MainController.m */,
63024AEB0F9640A8001B8E9B /* ITunesListener.m */,
63024AE90F964063001B8E9B /* ITunesListener.h */,
63D1D1020F980532004FBC05 /* lastfm.h */,
63D1D1030F980532004FBC05 /* lastfm.m */,
- 63D1D1A00F989847004FBC05 /* HistoryMenuController.h */,
- 63D1D1A10F989847004FBC05 /* HistoryMenuController.m */,
630EF82D1004ACC200EB28C2 /* AutoDash.h */,
630EF82E1004ACC200EB28C2 /* AutoDash.m */,
63451BC511BAD1C50087E3AB /* NSDictionary+Track.h */,
63451BC611BAD1C50087E3AB /* NSDictionary+Track.m */,
+ 6390167311CCE4DA00DCCC84 /* ShareWindowController.h */,
+ 6390167411CCE4DA00DCCC84 /* ShareWindowController.m */,
);
name = Classes;
sourceTree = "<group>";
@@ -311,12 +311,12 @@
buildActionMask = 2147483647;
files = (
8D11072D0486CEB800E47090 /* main.m in Sources */,
- 63D6DAF10F8396E000F4DCAA /* StatusItemController.m in Sources */,
+ 63D6DAF10F8396E000F4DCAA /* MainController.m in Sources */,
63024AEC0F9640A8001B8E9B /* ITunesListener.m in Sources */,
63D1D1040F980532004FBC05 /* lastfm.m in Sources */,
- 63D1D1A20F989847004FBC05 /* HistoryMenuController.m in Sources */,
630EF82F1004ACC200EB28C2 /* AutoDash.m in Sources */,
63451BC711BAD1C50087E3AB /* NSDictionary+Track.m in Sources */,
+ 6390167511CCE4DA00DCCC84 /* ShareWindowController.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
50 English.lproj/main.xib
View
@@ -268,10 +268,7 @@
<bool key="NSNoAutoenable">YES</bool>
</object>
<object class="NSCustomObject" id="1050762845">
- <string key="NSClassName">StatusItemController</string>
- </object>
- <object class="NSCustomObject" id="232807562">
- <string key="NSClassName">HistoryMenuController</string>
+ <string key="NSClassName">MainController</string>
</object>
<object class="NSUserDefaultsController" id="518255893">
<bool key="NSSharedInstance">YES</bool>
@@ -627,14 +624,6 @@
<int key="connectionID">466</int>
</object>
<object class="IBConnectionRecord">
- <object class="IBOutletConnection" key="connection">
- <string key="label">menu</string>
- <reference key="source" ref="232807562"/>
- <reference key="destination" ref="1020011467"/>
- </object>
- <int key="connectionID">474</int>
- </object>
- <object class="IBConnectionRecord">
<object class="IBActionConnection" key="connection">
<string key="label">startAtLogin:</string>
<reference key="source" ref="1050762845"/>
@@ -898,6 +887,14 @@
</object>
<int key="connectionID">696</int>
</object>
+ <object class="IBConnectionRecord">
+ <object class="IBOutletConnection" key="connection">
+ <string key="label">history_menu</string>
+ <reference key="source" ref="1050762845"/>
+ <reference key="destination" ref="1020011467"/>
+ </object>
+ <int key="connectionID">697</int>
+ </object>
</object>
<object class="IBMutableOrderedSet" key="objectRecords">
<object class="NSArray" key="orderedObjects">
@@ -1016,11 +1013,6 @@
<reference key="parent" ref="6461142"/>
</object>
<object class="IBObjectRecord">
- <int key="objectID">473</int>
- <reference key="object" ref="232807562"/>
- <reference key="parent" ref="0"/>
- </object>
- <object class="IBObjectRecord">
<int key="objectID">475</int>
<reference key="object" ref="601601618"/>
<reference key="parent" ref="1020011467"/>
@@ -1476,7 +1468,7 @@
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
- <string>{{613, 644}, {242, 193}}</string>
+ <string>{{613, 644}, {239, 193}}</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
<string>com.apple.InterfaceBuilder.CocoaPlugin</string>
@@ -1601,25 +1593,13 @@
</object>
</object>
<nil key="sourceID"/>
- <int key="maxID">696</int>
+ <int key="maxID">697</int>
</object>
<object class="IBClassDescriber" key="IBDocument.Classes">
<object class="NSMutableArray" key="referencedPartialClassDescriptions">
<bool key="EncodedWithXMLCoder">YES</bool>
<object class="IBPartialClassDescription">
- <string key="className">HistoryMenuController</string>
- <string key="superclassName">NSObject</string>
- <object class="NSMutableDictionary" key="outlets">
- <string key="NS.key.0">menu</string>
- <string key="NS.object.0">NSMenu</string>
- </object>
- <object class="IBClassDescriptionSource" key="sourceIdentifier">
- <string key="majorKey">IBProjectSource</string>
- <string key="minorKey">HistoryMenuController.h</string>
- </object>
- </object>
- <object class="IBPartialClassDescription">
- <string key="className">StatusItemController</string>
+ <string key="className">MainController</string>
<string key="superclassName">NSObject</string>
<object class="NSMutableDictionary" key="actions">
<bool key="EncodedWithXMLCoder">YES</bool>
@@ -1651,6 +1631,7 @@
<object class="NSArray" key="dict.sortedKeys">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>app_menu</string>
+ <string>history_menu</string>
<string>love</string>
<string>menu</string>
<string>share</string>
@@ -1661,6 +1642,7 @@
<object class="NSMutableArray" key="dict.values">
<bool key="EncodedWithXMLCoder">YES</bool>
<string>NSMenu</string>
+ <string>NSMenu</string>
<string>NSMenuItem</string>
<string>NSMenu</string>
<string>NSMenuItem</string>
@@ -1670,8 +1652,8 @@
</object>
</object>
<object class="IBClassDescriptionSource" key="sourceIdentifier">
- <string key="majorKey">IBProjectSource</string>
- <string key="minorKey">StatusItemController.h</string>
+ <string key="majorKey">IBUserSource</string>
+ <string key="minorKey"/>
</object>
</object>
</object>
30 HistoryMenuController.h
View
@@ -1,30 +0,0 @@
-/***************************************************************************
- * Copyright 2005-2009 Last.fm Ltd. *
- * Copyright 2010 Max Howell <max@methylblue.com *
- * *
- * 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., *
- * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
- ***************************************************************************/
-
-#import <Cocoa/Cocoa.h>
-
-
-@interface HistoryMenuController:NSObject{
- IBOutlet NSMenu* menu;
- NSMutableArray* tracks;
- NSDictionary* currentTrack;
-}
-
-@end
76 HistoryMenuController.m
View
@@ -1,76 +0,0 @@
-/***************************************************************************
- * Copyright 2005-2009 Last.fm Ltd. *
- * Copyright 2010 Max Howell <max@methylblue.com *
- * *
- * 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., *
- * 51 Franklin Steet, Fifth Floor, Boston, MA 02110-1301, USA. *
- ***************************************************************************/
-
-#import "HistoryMenuController.h"
-#import "NSDictionary+Track.h"
-#import "lastfm.h"
-
-
-@implementation HistoryMenuController
-
--(void)awakeFromNib
-{
- tracks = [NSMutableArray arrayWithCapacity:5];
-
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(onPlayerInfo:)
- name:@"playerInfo"
- object:nil];
-}
-
--(void)insert:(NSDictionary*)track
-{
- NSMenuItem* item = [menu itemAtIndex:0];
- if([item isEnabled] == false)
- [menu removeItem:item];
-
- item = [[NSMenuItem alloc] initWithTitle:track.prettyTitle action:@selector(clicked:) keyEquivalent:@""];
- [item setTarget:self];
- [item setRepresentedObject:track.url];
- [menu insertItem:item atIndex:0];
- [item release];
-
- // 18 items is about an hour
- if([menu numberOfItems] > 18)
- [menu removeItemAtIndex:15];
-}
-
--(void)onPlayerInfo:(NSNotification*)not
-{
- NSDictionary* track = [not userInfo];
- uint transition = [[track objectForKey:@"Transition"] unsignedIntValue];
-
- switch(transition){
- case TrackStarted:
- case PlaybackStopped:
- if(currentTrack)
- [self insert:currentTrack];
- [currentTrack release];
- currentTrack = track;
- [currentTrack retain];
- }
-}
-
--(void)clicked:(id)sender
-{
- [[NSWorkspace sharedWorkspace] openURL:[sender representedObject]];
-}
-
-@end
19 StatusItemController.h → MainController.h
View
@@ -25,11 +25,12 @@
@class ShareWindowController;
-@interface StatusItemController : NSObject <GrowlApplicationBridgeDelegate, LastfmDelegate>
+@interface MainController : NSObject <GrowlApplicationBridgeDelegate, LastfmDelegate>
{
NSStatusItem* status_item;
IBOutlet NSMenu* menu;
IBOutlet NSMenu* app_menu;
+ IBOutlet NSMenu* history_menu;
IBOutlet NSMenuItem* start_at_login;
IBOutlet NSMenuItem* status;
IBOutlet NSMenuItem* love;
@@ -51,19 +52,3 @@
-(IBAction)moreRecentHistory:(id)sender;
@end
-
-
-@interface ShareWindowController:NSWindowController
-{
- IBOutlet NSProgressIndicator* spinner;
- IBOutlet NSTextField* username;
- NSDictionary* track;
- Lastfm* lastfm;
-}
-
-@property(nonatomic, retain) NSDictionary* track;
-@property(nonatomic, retain) Lastfm* lastfm;
-
--(IBAction)submit:(id)sender;
-
-@end
61 StatusItemController.m → MainController.m
View
@@ -19,10 +19,11 @@
***************************************************************************/
#import "AutoDash.h"
+#import "ITunesListener.h"
#import "lastfm.h"
+#import "MainController.h"
#import "NSDictionary+Track.h"
-#import "ITunesListener.h"
-#import "StatusItemController.h"
+#import "ShareWindowController.h"
#import <Carbon/Carbon.h>
#import <WebKit/WebKit.h>
@@ -40,10 +41,10 @@ static OSStatus MyHotKeyHandler(EventHandlerCallRef ref, EventRef e, void* userd
GetEventParameter(e, kEventParamDirectObject, typeEventHotKeyID, NULL, sizeof(hkid), NULL, &hkid);
switch(hkid.id){
case 1:
- [(StatusItemController*)userdata tag:userdata];
+ [(MainController*)userdata tag:userdata];
break;
case 2:
- [(StatusItemController*)userdata share:userdata];
+ [(MainController*)userdata share:userdata];
break;
}
return noErr;
@@ -78,7 +79,7 @@ static LSSharedFileListItemRef audioscrobbler_session_login_item(LSSharedFileLis
}
-@implementation StatusItemController
+@implementation MainController
+(void)initialize
{
@@ -305,6 +306,33 @@ -(void)lastfm:(Lastfm*)lastfm metadata:(NSDictionary*)metadata betterdata:(NSDic
clickContext:nil];
}
+-(void)lastfm:(Lastfm*)lastfm scrobbled:(NSDictionary*)track failureMessage:(NSString*)message
+{
+ NSMenuItem* item = [history_menu itemAtIndex:0];
+ if(false == [item isEnabled])
+ [history_menu removeItem:item];
+
+ NSString* title = track.prettyTitle;
+ if (message)
+ title = [title stringByAppendingFormat:@" (Failed: %@)", message];
+ else
+ title = [title stringByAppendingFormat:@" (OK)"];
+
+ item = [[NSMenuItem alloc] initWithTitle:title action:@selector(historyItemClicked:) keyEquivalent:@""];
+ [item setTarget:self];
+ [item setRepresentedObject:track.url];
+ [history_menu insertItem:item atIndex:0];
+ [item release];
+
+ // 18 items is about an hour
+ if([history_menu numberOfItems] > 18)
+ [history_menu removeItemAtIndex:15];
+}
+
+-(void)historyItemClicked:(id)sender
+{
+ [[NSWorkspace sharedWorkspace] openURL:[sender representedObject]];
+}
-(void)growlNotificationWasClicked:(id)dict
{
@@ -413,26 +441,3 @@ -(IBAction)moreRecentHistory:(id)sender
}
@end
-
-
-
-@implementation ShareWindowController
-
-@synthesize track;
-@synthesize lastfm;
-
--(void)submit:(id)sender
-{
- [spinner startAnimation:self];
- [lastfm share:track with:[username stringValue]];
- [self close];
- [spinner stopAnimation:self];
-}
-
--(void)showWindow:(id)sender
-{
- [NSApp activateIgnoringOtherApps:YES]; //see above about:
- [super showWindow:sender];
-}
-
-@end
19 ShareWindowController.h
View
@@ -0,0 +1,19 @@
+// Created by Max Howell on 19/06/2010.
+#import <Cocoa/Cocoa.h>
+@class Lastfm;
+
+
+@interface ShareWindowController:NSWindowController
+{
+ IBOutlet NSProgressIndicator* spinner;
+ IBOutlet NSTextField* username;
+ NSDictionary* track;
+ Lastfm* lastfm;
+}
+
+@property(nonatomic, retain) NSDictionary* track;
+@property(nonatomic, retain) Lastfm* lastfm;
+
+-(IBAction)submit:(id)sender;
+
+@end
25 ShareWindowController.m
View
@@ -0,0 +1,25 @@
+// Created by Max Howell on 19/06/2010.
+#import "ShareWindowController.h"
+#import "lastfm.h"
+
+
+@implementation ShareWindowController
+
+@synthesize track;
+@synthesize lastfm;
+
+-(void)submit:(id)sender
+{
+ [spinner startAnimation:self];
+ [lastfm share:track with:[username stringValue]];
+ [self close];
+ [spinner stopAnimation:self];
+}
+
+-(void)showWindow:(id)sender
+{
+ [NSApp activateIgnoringOtherApps:YES]; //see above about:
+ [super showWindow:sender];
+}
+
+@end
1  lastfm.h
View
@@ -27,6 +27,7 @@
-(void)lastfm:(Lastfm*)lastfm requiresAuth:(NSURL*)url; // the user needs to visit this URL to auth
-(void)lastfm:(Lastfm*)lastfm metadata:(NSDictionary*)metadata betterdata:(NSDictionary*)betterdata;
-(void)lastfm:(Lastfm*)lastfm errorCode:(int)code errorMessage:(NSString*)message;
+-(void)lastfm:(Lastfm*)lastfm scrobbled:(NSDictionary*)track failureMessage:(NSString*)message;
@end
248 lastfm.m
View
@@ -24,6 +24,7 @@
#define KEYCHAIN_NAME "fm.last.Audioscrobbler"
+enum HTTPMethod { GET, POST };
static NSString* md5(NSString* s)
{
@@ -51,26 +52,44 @@ -(NSXMLDocument*)readResponse:(NSMutableURLRequest*)rq;
@interface LastfmError : NSObject {
int code;
NSString* message;
+ NSString* method;
}
@property(assign) int code;
@property(assign) NSString* message;
-+(LastfmError*)badResponse;
+@property(assign) NSString* method;
++(LastfmError*)badResponse:(NSString*)method;
@end
@implementation LastfmError
-@synthesize code, message;
-+(LastfmError*)badResponse {
+@synthesize code, method, message;
++(id)badResponse:(NSString*)method {
LastfmError* e = [[[LastfmError alloc] init] autorelease];
e.code = 11;
e.message = @"Last.fm is not responding, please try again later";
+ e.method = method;
return e;
}
-+(LastfmError*)unexpectedError:(NSString*)msg {
++(id)unexpectedError:(NSString*)msg {
LastfmError* e = [[[LastfmError alloc] init] autorelease];
- e.code = 100;
+ e.code = -1;
e.message = msg;
return e;
}
++(id)authenticationRequired:(NSString*)method {
+ LastfmError* e = [[[LastfmError alloc] init] autorelease];
+ e.code = 9;
+ e.message = @"Authentication required";
+ e.method = method;
+ return e;
+}
+-(NSString*)prettyMessage {
+ return method
+ ? [message stringByAppendingFormat:@" for method: %@", method]
+ : message;
+}
+-(void)setMessage:(NSString*)s {
+ message = s;
+}
@end
@@ -108,6 +127,28 @@ +(NSURL*)urlForUser:(NSString*)username
#pragma mark HTTP
+-(NSXMLDocument*)get:(NSMutableDictionary*)params to:(NSString*)method
+{
+ NSMutableString* url = [NSMutableString stringWithCapacity:256];
+ [url appendString:@"http://ws.audioscrobbler.com/2.0/?"];
+ for (id key in params) {
+ [url appendString:key];
+ [url appendString:@"="];
+ [url appendString:[[params objectForKey:key] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
+ [url appendString:@"&"];
+ }
+ [url appendString:@"api_key=" LASTFM_API_KEY "&"];
+ [url appendString:@"method="];
+ [url appendString:method];
+
+ NSMutableURLRequest* rq = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
+ cachePolicy:NSURLRequestReloadIgnoringCacheData
+ timeoutInterval:10];
+ [rq setHTTPMethod:@"GET"];
+
+ return [self readResponse:rq];
+}
+
static NSString* signature(NSMutableDictionary* params)
{
NSArray* keys = [[params allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
@@ -120,34 +161,6 @@ +(NSURL*)urlForUser:(NSString*)username
return md5(s);
}
--(NSXMLDocument*)get:(NSMutableDictionary*)params to:(NSString*)method
-{
- @try {
- NSMutableString* url = [NSMutableString stringWithCapacity:256];
- [url appendString:@"http://ws.audioscrobbler.com/2.0/?"];
- for (id key in params) {
- [url appendString:key];
- [url appendString:@"="];
- [url appendString:[[params objectForKey:key] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
- [url appendString:@"&"];
- }
- [url appendString:@"api_key=" LASTFM_API_KEY "&"];
- [url appendString:@"method="];
- [url appendString:method];
-
- NSMutableURLRequest* rq = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
- cachePolicy:NSURLRequestReloadIgnoringCacheData
- timeoutInterval:10];
- [rq setHTTPMethod:@"GET"];
-
- return [self readResponse:rq];
- }
- @catch (LastfmError* e) {
- [delegate lastfm:self errorCode:e.code errorMessage:e.message];
- }
- return nil;
-}
-
static NSData* signed_post_body(NSMutableDictionary* params)
{
NSMutableString* s = [NSMutableString stringWithCapacity:256];
@@ -164,52 +177,78 @@ -(NSXMLDocument*)get:(NSMutableDictionary*)params to:(NSString*)method
-(NSXMLDocument*)post:(NSMutableDictionary*)params to:(NSString*)method
{
- @try {
- if (!sk && !token) goto auth;
- if (!sk && token) [self getSession];
+ if (!sk && !token) { @throw [LastfmError authenticationRequired:method]; }
+ if (!sk && token) [self getSession];
+
+ [params setObject:sk forKey:@"sk"];
+ [params setObject:@LASTFM_API_KEY forKey:@"api_key"];
+ [params setObject:method forKey:@"method"];
+ NSData* body = signed_post_body(params);
+
+ NSMutableURLRequest* rq = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://post.audioscrobbler.com/2.0/"]
+ cachePolicy:NSURLRequestReloadIgnoringCacheData
+ timeoutInterval:10];
+ [rq setHTTPMethod:@"POST"];
+ [rq setHTTPBody:body];
+ [rq setValue:[[NSNumber numberWithInteger:[body length]] stringValue] forHTTPHeaderField:@"Content-Length"];
+ [rq setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
+ return [self readResponse:rq];
+}
- [params setObject:sk forKey:@"sk"];
- [params setObject:@LASTFM_API_KEY forKey:@"api_key"];
- [params setObject:method forKey:@"method"];
- NSData* body = signed_post_body(params);
-
- NSMutableURLRequest* rq = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://post.audioscrobbler.com/2.0/"]
- cachePolicy:NSURLRequestReloadIgnoringCacheData
- timeoutInterval:10];
- [rq setHTTPMethod:@"POST"];
- [rq setHTTPBody:body];
- [rq setValue:[[NSNumber numberWithInteger:[body length]] stringValue] forHTTPHeaderField:@"Content-Length"];
- [rq setValue:@"application/x-www-form-urlencoded; charset=UTF-8" forHTTPHeaderField:@"Content-Type"];
- return [self readResponse:rq];
- }
- @catch (LastfmError* e) {
- switch (e.code) {
+-(void)handleError:(LastfmError*)e
+{
+ switch (e.code) {
case 15: // This token has expired
[token release];
token = nil;
case 9: // Invalid session key - Please re-authenticate
[sk release];
sk = nil;
- case 14: // This token has not been authorized
- goto auth;
+ case 14:
+ @try { // This token has not been authorized
+ if (!token) token = [[self getToken] retain];
+ NSString* url = [NSString stringWithFormat:@"http://www.last.fm/api/auth/?api_key=" LASTFM_API_KEY "&token=%@", token];
+ [delegate lastfm:self requiresAuth:[NSURL URLWithString:url]];
+ break;
+ }
+ @catch (LastfmError* ee) {
+ e = ee; // fall through to default case
+ }
default:
- [delegate lastfm:self errorCode:e.code errorMessage:e.message];
- return nil;
- }
+ [delegate lastfm:self errorCode:e.code errorMessage:e.prettyMessage];
+ break;
}
-
-auth:
+}
+
+-(NSXMLDocument*)request:(enum HTTPMethod)http_method params:(NSMutableDictionary*)params to:(NSString*)lastfm_method
+{
@try {
- if (!token) token = [[self getToken] retain];
- NSString* url = [NSString stringWithFormat:@"http://www.last.fm/api/auth/?api_key=" LASTFM_API_KEY "&token=%@", token];
- [delegate lastfm:self requiresAuth:[NSURL URLWithString:url]];
+ switch (http_method) {
+ case GET:
+ return [self get:params to:lastfm_method];
+ case POST:
+ return [self post:params to:lastfm_method];
+ }
}
@catch (LastfmError* e) {
- [delegate lastfm:self errorCode:e.code errorMessage:e.message];
+ [self handleError:e];
}
return nil;
}
+static NSString* extract_method(NSURLRequest* rq)
+{
+ NSString* query = [rq.HTTPMethod isEqualToString:@"GET"]
+ ? [rq.URL query]
+ : [[[NSString alloc] initWithData:[rq HTTPBody] encoding:NSUTF8StringEncoding] autorelease];
+
+ for (NSString* part in [query componentsSeparatedByString:@"&"])
+ if ([[part substringToIndex:7] isEqualToString:@"method="])
+ return [part substringFromIndex:7];
+
+ return @"method.unknown";
+}
+
-(NSXMLDocument*)readResponse:(NSMutableURLRequest*)rq
{
[rq setValue:@"com.methylblue.Audioscrobbler" forHTTPHeaderField:@"User-Agent"];
@@ -219,19 +258,22 @@ -(NSXMLDocument*)readResponse:(NSMutableURLRequest*)rq
NSData* data = [NSURLConnection sendSynchronousRequest:rq returningResponse:&headers error:&error];
if (error)
- @throw [LastfmError badResponse];
+ @throw [LastfmError badResponse:extract_method(rq)];
NSXMLDocument* xml = [[[NSXMLDocument alloc] initWithData:data options:NSXMLNodeOptionsNone error:nil] autorelease];
- bool ok = [[[[xml rootElement] attributeForName:@"status"] stringValue] isEqualToString:@"ok"];
+ bool ok = [[xml.rootElement attributeForName:@"status"].stringValue isEqualToString:@"ok"];
if (!ok) {
- NSXMLElement* ee = [[[xml rootElement] elementsForName:@"error"] lastObject];
-
- if (!ee) @throw [LastfmError badResponse];
+ NSXMLElement* ee = [xml.rootElement elementsForName:@"error"].lastObject;
+ if (!ee)
+ @throw [LastfmError badResponse:extract_method(rq)];
+ const int code = [ee attributeForName:@"code"].stringValue.intValue;
+
LastfmError* e = [[[LastfmError alloc] init] autorelease];
- e.code = [[[ee attributeForName:@"code"] stringValue] intValue];
+ e.code = code;
e.message = [ee stringValue];
+ e.method = extract_method(rq);
@throw e;
}
@@ -243,22 +285,20 @@ -(NSXMLDocument*)readResponse:(NSMutableURLRequest*)rq
-(NSString*)getToken
{
NSXMLDocument* xml = [self get:[NSMutableDictionary dictionary] to:@"auth.gettoken"];
- return [[[xml.rootElement elementsForName:@"token"] lastObject] stringValue];
+ return [[xml.rootElement elementsForName:@"token"].lastObject stringValue];
}
-static void inline save(NSString* username, NSString* sk) {
+static void inline save(NSString* username, NSString* sk)
+{
const char* cstr = [username UTF8String];
- OSStatus err = SecKeychainAddGenericPassword(NULL, //default keychain
- sizeof(KEYCHAIN_NAME),
- KEYCHAIN_NAME,
- strlen(cstr),
- cstr,
- 32,
- [sk UTF8String],
- NULL);
- if (err != noErr)
- @throw [LastfmError unexpectedError:[NSString stringWithFormat:@"%s", GetMacOSStatusCommentString(err)]];
-
+ SecKeychainAddGenericPassword(NULL, //default keychain
+ sizeof(KEYCHAIN_NAME),
+ KEYCHAIN_NAME,
+ strlen(cstr),
+ cstr,
+ 32,
+ [sk UTF8String],
+ NULL);
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:username forKey:@"Username"];
[defaults synchronize];
@@ -273,16 +313,16 @@ -(void)getSession
#undef DICT
NSXMLDocument* xml = [self get:params to:@"auth.getsession"];
-
+
[token release]; // consumed
token = nil;
- NSXMLElement* session = [[[xml rootElement] elementsForName:@"session"] lastObject];
- sk = [[[[session elementsForName:@"key"] lastObject] stringValue] retain];
- username = [[[[session elementsForName:@"name"] lastObject] stringValue] retain];
+ NSXMLElement* session = [xml.rootElement elementsForName:@"session"].lastObject;
+ sk = [[[session elementsForName:@"key"].lastObject stringValue] retain];
+ username = [[[session elementsForName:@"name"].lastObject stringValue] retain];
if (!username || !sk)
- @throw [LastfmError badResponse];
+ @throw [LastfmError badResponse:@"auth.getsession"];
save(username, sk);
}
@@ -317,7 +357,7 @@ -(void)love:(NSDictionary*)track
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:5];
PACK(dict, track);
- [self post:dict to:@"track.love"];
+ [self request:POST params:dict to:@"track.love"];
}
-(void)share:(NSDictionary*)track with:(NSString*)user
@@ -328,20 +368,24 @@ -(void)share:(NSDictionary*)track with:(NSString*)user
PACK(dict, track);
[dict setObject:user forKey:@"recipient"];
- [self post:dict to:@"track.share"];
+ [self request:POST params:dict to:@"track.share"];
}
-(void)scrobble:(NSDictionary*)track startTime:(time_t)start_time
{
- if (!track) return;
-
- NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:7];
- PACK_MOAR(dict, track);
- [dict setObject:[[NSNumber numberWithUnsignedInt:start_time] stringValue] forKey:@"timestamp"];
-
- //TODO rest of optional parameters including albumArtist
-
- [self post:dict to:@"track.scrobble"];
+ if (!track)
+ return;
+ @try {
+ NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:7];
+ PACK_MOAR(dict, track);
+ [dict setObject:[[NSNumber numberWithUnsignedInt:start_time] stringValue] forKey:@"timestamp"];
+ [self post:dict to:@"track.scrobble"];
+ [delegate lastfm:self scrobbled:track failureMessage:nil];
+ }
+ @catch (LastfmError* e) {
+ [delegate lastfm:self scrobbled:track failureMessage:e.message];
+ [self handleError:e];
+ }
}
-(void)updateNowPlaying:(NSDictionary*)track
@@ -351,7 +395,7 @@ -(void)updateNowPlaying:(NSDictionary*)track
NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithCapacity:6];
PACK_MOAR(dict, track);
- NSXMLDocument* xml = [self post:dict to:@"user.updateNowPlaying"];
+ NSXMLDocument* xml = [self request:POST params:dict to:@"user.updateNowPlaying"];
#define NODE(x) [[[[xml rootElement] elementsForName:x] lastObject] stringValue]
NSString* Artist = NODE(@"artist");
@@ -372,6 +416,7 @@ -(void)updateNowPlaying:(NSDictionary*)track
-(id)initWithDelegate:(id)d
{
+ token = nil;
delegate = d;
#ifdef __AS_DEBUGGING__
@@ -381,9 +426,7 @@ -(id)initWithDelegate:(id)d
#endif
username = [[NSUserDefaults standardUserDefaults] stringForKey:@"Username"];
- if (!username)
- return self;
- if (username.length == 0) {
+ if (!username || username.length == 0) {
username = nil;
return self;
}
@@ -400,7 +443,6 @@ -(id)initWithDelegate:(id)d
&n,
&key,
NULL);
-
if (err == noErr) {
sk = [[NSString alloc] initWithBytes:key length:32 encoding:NSUTF8StringEncoding];
SecKeychainItemFreeContent(NULL, key);
Please sign in to comment.
Something went wrong with that request. Please try again.