Skip to content
Newer
Older
100644 340 lines (297 sloc) 10.9 KB
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
1 //
2 // SYController.m
2283d20 renamed Last.fm.framework to Scribbler
Matt Patenaude authored
3 // Scribbler
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
4 //
5 // Created by Matt Patenaude on 11/11/09.
d5cd245 added copyright notices to Scrobbly, changed identifier to com.13bold…
Matt Patenaude authored
6 // Copyright (C) 2009 {13bold}.
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 // THE SOFTWARE.
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
25 //
26
27 #import "SYController.h"
28 #import "MPTidbits.h"
29 #import "EMKeychainItem.h"
30 #import "EMKeychainProxy.h"
31 #import "SYUIController.h"
32
33
34 @implementation SYController
35
36 #pragma mark Initializers
37 + (void)initialize
38 {
39 NSDictionary *defaults = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO], @"LastFMConfigured", @"", @"LastFMUsername", nil];
40 [[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
41 }
42 - (id)init
43 {
44 if (self = [super init])
45 {
46 recentTracks = [[NSMutableArray alloc] init];
47 wasPlaying = NO;
48 }
49 return self;
50 }
51
52 #pragma mark Deallocator
53 - (void)dealloc
54 {
55 [recentTracks release];
56 [super dealloc];
57 }
58
59 #pragma mark Application delegate methods
60 - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
61 {
62 // setup for the API
63 // We'll use this variable for when we need to switch back and
64 // forth between the web browser
65 authorizationPending = NO;
66
67 // First, let's setup the web service object
68 // You can obtain the API key and shared secret on your API info page
69 // - http://www.last.fm/api/account
70
71 LFWebService *lastfm = [LFWebService sharedWebService];
72 [lastfm setDelegate:self];
73 [lastfm setAPIKey:@"71385ac7bf43e8219249ed14064fbb4b"];
74 [lastfm setSharedSecret:@"3414b25bb2f1e1f75ba2d54a94ef102c"];
75
76 // We'll also set our client ID for scrobbling
77 // You can obtain one of these by contacting Last.fm
78 // - http://www.last.fm/api/submissions#1.1
c47be25 added client identifier to Scrobbly, ready for use
Matt Patenaude authored
79 [lastfm setClientID:@"sby"];
80 [lastfm setClientVersion:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]];
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
81
82 // In order to run, we need a valid session key
83 // First, we'll check to see if we have one. If we do,
84 // we'll set it, then test it. Otherwise, we'll wait for
85 // someone to click the "Connect" button.
86 [self connectWithStoredCredentials];
87
88 // register for our iTunes notifications
89 [[NSDistributedNotificationCenter defaultCenter] addObserver:self selector:@selector(playerInfoChanged:) name:@"com.apple.iTunes.playerInfo" object:nil];
90 }
91 - (void)applicationDidBecomeActive:(NSNotification *)aNotification
92 {
93 // If we have a pending authorization, this is our
94 // cue to start trying to validate it, since the user likely
95 // just switched back from the browser window
96 if (authorizationPending)
97 {
98 authorizationPending = NO;
c7a5f42 miscellaneous Scrobbly app updates; added URL escaping for all specia…
Matt Patenaude authored
99 [self completeAuthorization:nil];
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
100 }
101 }
d5cd245 added copyright notices to Scrobbly, changed identifier to com.13bold…
Matt Patenaude authored
102 - (BOOL)applicationOpenUntitledFile:(NSApplication *)theApplication
103 {
104 [mainWindow makeKeyAndOrderFront:self];
105 return YES;
106 }
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
107
108 #pragma mark Connection methods
109 - (IBAction)connectWithLastFM:(id)sender
110 {
111 // This means we're going to force establish a new Last.fm session
112 [[LFWebService sharedWebService] establishNewSession];
113
114 // Adjust the UI to show status
115 [ui showPreAuthMessage];
116 }
117 - (IBAction)disconnectFromLastFM:(id)sender
118 {
119 // We need to get the username
120 NSString *theUser = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastFMUsername"];
121
122 // We need to delete the user default information
123 [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"LastFMConfigured"];
124 [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"LastFMUsername"];
125
126 // And clear the Keychain info
127 NSString *keychainService = [NSString stringWithFormat:@"Last.fm (%@)", [[NSBundle mainBundle] bundleIdentifier]];
128 EMGenericKeychainItem *keyItem = [[EMKeychainProxy sharedProxy] genericKeychainItemForService:keychainService withUsername:theUser];
129 if (keyItem)
130 [keyItem setPassword:@""];
131
132 // Finally, clear out the web service...
133 LFWebService *lastfm = [LFWebService sharedWebService];
134 [lastfm setSessionUser:nil];
135 [lastfm setSessionKey:nil];
136
137 // ... and update the UI
138 [ui showConnectMessage];
139 }
140 - (void)connectWithStoredCredentials
141 {
142 // we have stored credentials, so we'll grab the user from the defaults,
143 // then grab the session key from the keychain...
144
145 if ([[NSUserDefaults standardUserDefaults] boolForKey:@"LastFMConfigured"])
146 {
147 NSString *theUser = [[NSUserDefaults standardUserDefaults] objectForKey:@"LastFMUsername"];
148
149 NSString *keychainService = [NSString stringWithFormat:@"Last.fm (%@)", [[NSBundle mainBundle] bundleIdentifier]];
150 EMGenericKeychainItem *keyItem = [[EMKeychainProxy sharedProxy] genericKeychainItemForService:keychainService withUsername:theUser];
151 if (keyItem)
152 {
153 // we'll set both the user and session key in the web service
154 LFWebService *lastfm = [LFWebService sharedWebService];
155 [lastfm setSessionUser:theUser];
156 [lastfm setSessionKey:[keyItem password]];
157
158 // and then attempt to validate the credentials
159 [lastfm validateSessionCredentials];
160
161 // Adjust the UI
162 [ui showValidatingMessage];
163 }
164 }
165 }
c7a5f42 miscellaneous Scrobbly app updates; added URL escaping for all specia…
Matt Patenaude authored
166 - (void)completeAuthorization:(NSNotification *)theNotification
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
167 {
168 // And now we finish authorization
169 [[LFWebService sharedWebService] finishSessionAuthorization];
170 }
171
172 #pragma mark Web service delegate methods
173 - (void)sessionNeedsAuthorizationViaURL:(NSURL *)theURL
174 {
175 // OK, so the first stage is done; we'll update the
176 // UI to match the current status,
177 // then open up the web browser to have the user allow our demo app
178 // access
179 [ui showWaitingMessage];
180
181 [[NSWorkspace sharedWorkspace] openURL:theURL];
182 authorizationPending = YES;
183 }
184 - (void)sessionAuthorizationStillPending
185 {
186 // We tried to authorize the session, but the user
187 // isn't done in the web browser yet. Wait 5 seconds,
188 // then try again.
189
190 [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(completeAuthorization:) userInfo:nil repeats:NO];
191 }
192 - (void)sessionAuthorizationFailed
193 {
194 // We failed. Epically.
195 [ui showConnectMessage];
196 }
197 - (void)sessionCreatedWithKey:(NSString *)theKey user:(NSString *)theUser
198 {
199 // The session key will be valid for future uses -- it never
200 // expires unless explicitly revoked by the Last.fm user.
201 // Therefore, we can store the user as a default, and then store
202 // the key in the Keychain for future use.
203
204 [[NSUserDefaults standardUserDefaults] setObject:theUser forKey:@"LastFMUsername"];
205 [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"LastFMConfigured"];
206
207 NSString *keychainService = [NSString stringWithFormat:@"Last.fm (%@)", [[NSBundle mainBundle] bundleIdentifier]];
208 EMGenericKeychainItem *keyItem = [[EMKeychainProxy sharedProxy] genericKeychainItemForService:keychainService withUsername:theUser];
209 if (keyItem)
210 [keyItem setPassword:theKey];
211 else
212 [[EMKeychainProxy sharedProxy] addGenericKeychainItemForService:keychainService withUsername:theUser password:theKey];
213
214 // Hooray! we're up and running
215 [ui showConnectedWithUser:theUser];
216 }
217
218 - (void)sessionValidatedForUser:(NSString *)theUser
219 {
220 // Hooray! we're up and running
221 [ui showConnectedWithUser:theUser];
222 }
223 - (void)sessionInvalidForUser:(NSString *)theUser
224 {
225 // We failed. Epically.
226 [ui showConnectMessage];
227 }
228 - (void)sessionKeyRevoked:(NSString *)theKey forUser:(NSString *)theUser
229 {
230 // The key was revoked, so we disconnect from Last.fm permanently
231 [self disconnectFromLastFM:self];
232 }
233
234 - (void)scrobblerClient:(NSString *)theClientID bannedForVersion:(NSString *)theClientVersion
235 {
236 NSLog(@"Error: client banned (%@ - %@)", theClientID, theClientVersion);
237 }
238
239 - (void)scrobbleSucceededForTrack:(LFTrack *)theTrack
240 {
241 NSString *tname = [NSString stringWithFormat:@"%@ (%@)", [theTrack title], [theTrack artist]];
242 [recentTracks insertObject:tname atIndex:0];
243 [trackTableView reloadData];
244 }
245 - (void)scrobbleFailedForTrack:(LFTrack *)theTrack error:(NSError *)theError willRetry:(BOOL)willRetry
246 {
247 NSLog(@"Scrobble failed: %@ (%@) - %@", [theTrack title], [theTrack artist], [theError localizedDescription]);
248 }
249
250
251 #pragma mark Tracking methods
252 - (void)playerInfoChanged:(NSNotification *)theNotification
253 {
254 NSDictionary *info = [theNotification userInfo];
255
256 if ([[info objectForKey:@"Player State"] isEqualToString:@"Stopped"])
257 {
258 // Scrobble if it's necessary
259 if (currentTrack)
260 {
261 [currentTrack stop];
262 [currentTrack release];
263 currentTrack = nil;
264 wasPlaying = NO;
265 }
266 [ui nothingPlaying];
267 return;
268 }
269
270 // check persistentID
271 NSUInteger theID = [(NSNumber *)[info objectForKey:@"PersistentID"] unsignedIntegerValue];
272 if (currentTrackID != theID)
273 {
274 // Scrobble if it's necessary
275 if (currentTrack)
276 {
277 [currentTrack stop];
278 [currentTrack release];
279 currentTrack = nil;
280 wasPlaying = NO;
281 }
282
283 // the track changed
284 if (![info containsKey:@"Name"] || ![info containsKey:@"Artist"])
285 {
286 [ui unusableTrack];
287 return;
288 }
289
290 NSString *trackName = [info objectForKey:@"Name"];
291 NSString *trackArtist = [info objectForKey:@"Artist"];
292 CGFloat totalTime = [(NSNumber *)[info objectForKey:@"Total Time"] floatValue] / 1000.0;
293
294 LFTrack *theTrack = [LFTrack trackWithTitle:trackName artist:trackArtist duration:totalTime];
295 if ([info containsKey:@"Album"])
296 [theTrack setAlbum:[info objectForKey:@"Album"]];
297 if ([info containsKey:@"Track Number"])
298 [theTrack setAlbumPosition:[(NSNumber *)[info objectForKey:@"Track Number"] unsignedIntegerValue]];
299
300 NSString *subline = ([info containsKey:@"Album"]) ? [NSString stringWithFormat:@"%@ (on \"%@\")", trackArtist, [info objectForKey:@"Album"]] : trackArtist;
301 [ui displayTrack:trackName subline:subline];
302
303 currentTrack = [theTrack retain];
82fcdcf fixed a bug where songs would be scrobbled on pause if enough had bee…
Matt Patenaude authored
304 currentTrackID = theID;
7db14ff created second (useful) demo app, Scrobbly, a standalone scrobbler
Matt Patenaude authored
305 }
306
307 if ([[info objectForKey:@"Player State"] isEqualToString:@"Playing"] && !wasPlaying)
308 {
309 [currentTrack play];
310 wasPlaying = YES;
311 }
312 if ([[info objectForKey:@"Player State"] isEqualToString:@"Paused"] && wasPlaying)
313 {
314 [currentTrack pause];
315 wasPlaying = NO;
316 }
317 }
318 - (IBAction)loveTrack:(id)sender
319 {
320 if (currentTrack)
321 [currentTrack love];
322 }
323 - (IBAction)banTrack:(id)sender
324 {
325 if (currentTrack)
326 [currentTrack ban];
327 }
328
329 #pragma mark Table data source methods
330 - (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
331 {
332 return [recentTracks count];
333 }
334 - (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
335 {
336 return [recentTracks objectAtIndex:rowIndex];
337 }
338
339 @end
Something went wrong with that request. Please try again.