Submissions work now. Committing before I break something by adding queues.
[MenuTunes.git] / AudioscrobblerController.m
1 /*
2  *      MenuTunes
3  *  AudioscrobblerController
4  *    Audioscrobbler Support Class
5  *
6  *  Original Author : Kent Sutherland <kent.sutherland@ithinksw.com>
7  *   Responsibility : Kent Sutherland <kent.sutherland@ithinksw.com>
8  *
9  *  Copyright (c) 2005 iThink Software.
10  *  All Rights Reserved
11  *
12  */
13
14 #import "AudioscrobblerController.h"
15 #import <openssl/evp.h>
16
17 static AudioscrobblerController *_sharedController = nil;
18
19 @implementation AudioscrobblerController
20
21 + (void)load
22 {
23         NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
24         [[AudioscrobblerController sharedController] submitTrack:@"Stairway To Heaven" artist:@"Led Zeppelin" album:@"Led Zeppelin IV" length:483];
25         [pool release];
26 }
27
28 + (AudioscrobblerController *)sharedController
29 {
30         if (!_sharedController) {
31                 _sharedController = [[AudioscrobblerController alloc] init];
32         }
33         return _sharedController;
34 }
35
36 - (id)init
37 {
38         if ( (self = [super init]) ) {
39                 _handshakeCompleted = NO;
40                 _responseData = nil;
41                 _md5Challenge = nil;
42                 _postURL = nil;
43                 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAudioscrobblerNotification:) name:nil object:self];
44         }
45         return self;
46 }
47
48 - (void)dealloc
49 {
50         [_md5Challenge release];
51         [_postURL release];
52         [_responseData release];
53         [super dealloc];
54 }
55
56 - (void)attemptHandshake
57 {
58         NSString *version = [[[NSBundle bundleWithPath:[[NSWorkspace sharedWorkspace] fullPathForApplication:@"iTunes.app"]] infoDictionary] objectForKey:@"CFBundleVersion"], *user = @"Tristrex";
59         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"http://post.audioscrobbler.com/?hs=true&p=1.1&c=tst&v=%@&u=%@", version, user]];
60         NSURLConnection *connection;
61         
62         _currentStatus = AudioscrobblerRequestingHandshakeStatus;
63         _responseData = [[NSMutableData alloc] init];
64         connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30] delegate:self];
65 }
66
67 - (BOOL)handshakeCompleted
68 {
69         return _handshakeCompleted;
70 }
71
72 - (void)submitTrack:(NSString *)title artist:(NSString *)artist album:(NSString *)album length:(int)length
73 {
74         if (!_handshakeCompleted) {
75                 [self attemptHandshake];
76                 return;
77         }
78         
79         //What we eventually want is a submission list that sends backlogs also
80         NSMutableURLRequest *request = [[NSURLRequest requestWithURL:_postURL cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:30] mutableCopy];
81         NSString *responseHash = @"", *requestBody;
82         
83         char *pass = "waffles";
84         unsigned char *buffer;
85         EVP_MD_CTX ctx;
86         int i;
87         
88         buffer = malloc(EVP_MD_size(EVP_md5()));
89         
90         EVP_DigestInit(&ctx, EVP_md5());
91         EVP_DigestUpdate(&ctx, pass, strlen(pass));
92         EVP_DigestFinal(&ctx, buffer, NULL);
93         
94         for (i = 0; i < 16; i++) {
95                 responseHash = [responseHash stringByAppendingFormat:@"%0.2x", buffer[i]];
96         }
97         
98         free(buffer);
99         buffer = malloc(EVP_MD_size(EVP_md5()));
100         char *cat = (char *)[[responseHash stringByAppendingString:_md5Challenge] UTF8String];
101         EVP_DigestInit(&ctx, EVP_md5());
102         EVP_DigestUpdate(&ctx, cat, strlen(cat));
103         EVP_DigestFinal(&ctx, buffer, NULL);
104         
105         responseHash = @"";
106         for (i = 0; i < 16; i++) {
107                 responseHash = [responseHash stringByAppendingFormat:@"%0.2x", buffer[i]];
108         }
109         free(buffer);
110         
111         requestBody = (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (CFStringRef)[NSString stringWithFormat:@"u=%@&s=%@&a[0]=%@&t[0]=%@&b[0]=%@&m[0]=&l[0]=%i&i[0]=%@", @"Tristrex", responseHash, artist, title, album, length, [[NSDate date] descriptionWithCalendarFormat:@"%Y-%m-%d %H:%M:%S" timeZone:nil locale:nil]], NULL, NULL, kCFStringEncodingUTF8);
112         [request setHTTPMethod:@"POST"];
113         [request setHTTPBody:[requestBody dataUsingEncoding:NSUTF8StringEncoding]];
114         _currentStatus = AudioscrobblerSubmittingTrackStatus;
115         _responseData = [[NSMutableData alloc] init];
116         [NSURLConnection connectionWithRequest:request delegate:self];
117         CFRelease(requestBody);
118         [request release];
119 }
120
121 - (void)handleAudioscrobblerNotification:(NSNotification *)note
122 {
123         if ([[note name] isEqualToString:@"AudioscrobblerHandshakeComplete"]) {
124                 [[AudioscrobblerController sharedController] submitTrack:@"Good Times Bad Times" artist:@"Led Zeppelin" album:@"Led Zeppelin I" length:166];
125         }
126 }
127
128 #pragma mark -
129
130 - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
131 {
132         NSLog(@"Failed with an error: %@", error);
133 }
134
135 - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
136 {
137         [_responseData appendData:data];
138 }
139
140 - (void)connectionDidFinishLoading:(NSURLConnection *)connection
141 {
142         NSString *string = [[NSString alloc] initWithData:_responseData encoding:NSASCIIStringEncoding];
143         
144         if (_currentStatus == AudioscrobblerRequestingHandshakeStatus) {
145                 NSArray *lines = [string componentsSeparatedByString:@"\n"];
146                 NSString *responseAction;
147                 if ([lines count] < 2) {
148                         //We have an error
149                 }
150                 responseAction = [lines objectAtIndex:0];
151                 if ([responseAction isEqualToString:@"UPTODATE"]) {
152                         if ([lines count] >= 4) {
153                                 _md5Challenge = [[lines objectAtIndex:1] retain];
154                                 _postURL = [[NSURL alloc] initWithString:[lines objectAtIndex:2]];
155                                 _handshakeCompleted = YES;
156                                 [[NSNotificationCenter defaultCenter] postNotificationName:@"AudioscrobblerHandshakeComplete" object:self];
157                         } else {
158                                 //We have an error
159                         }
160                         //Something
161                 } else if (([responseAction length] > 6) && [[responseAction substringToIndex:5] isEqualToString:@"UPDATE"]) {
162                         //Something plus update action
163                 } else if (([responseAction length] > 6) && [[responseAction substringToIndex:5] isEqualToString:@"FAILED"]) {
164                         //We have an error
165                 } else if ([responseAction isEqualToString:@"BADUSER"]) {
166                         //We have an error
167                 } else {
168                         //We have an error
169                 }
170         } else if (_currentStatus == AudioscrobblerSubmittingTrackStatus) {
171                 NSLog(string);
172         }
173         
174         [string release];
175 }
176
177 @end