7f6ae5fc7fffcf9d75043552155d094263aaffcb
[pithos-ios] / Classes / OpenStackRequest.m
1 //
2 //  OpenStackRequest.m
3 //  OpenStack
4 //
5 //  Created by Mike Mayo on 10/8/10.
6 //  The OpenStack project is provided under the Apache 2.0 license.
7 //
8
9 #import "OpenStackRequest.h"
10 #import "Provider.h"
11 #import "OpenStackAccount.h"
12 #import "JSON.h"
13 #import "Container.h"
14 #import "StorageObject.h"
15 #import "Folder.h"
16 #import "AccountManager.h"
17 #import "APICallback.h"
18 #import "APILogEntry.h"
19 #import "NSString+Conveniences.h"
20
21 static NSRecursiveLock *accessDetailsLock = nil;
22
23 @implementation OpenStackRequest
24
25 @synthesize account, callback, retriedCount, errorAlerter;
26
27 - (BOOL)isSuccess {
28         return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
29 }
30
31 - (void)notify:(NSString *)name {
32     NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
33     NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo];
34     [[NSNotificationCenter defaultCenter] postNotification:notification];
35 }
36
37 - (void)notify {
38     NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description]];
39     NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description], self.callback.uuid];
40
41     NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
42
43     NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
44     [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
45
46     NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
47     [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
48     
49 }
50
51 - (NSString *)responseString {
52     if (retried) {
53         return [retriedRequest responseString];
54     } else {
55         return [super responseString];
56     }
57 }
58
59 - (NSData *)responseData {
60     if (retried) {
61         return [retriedRequest responseData];
62     } else {
63         return [super responseData];
64     }
65 }
66
67 - (NSDictionary *)responseHeaders {
68     if (retried) {
69         return [retriedRequest responseHeaders];
70     } else {
71         return [super responseHeaders];
72     }
73 }
74
75 - (int)responseStatusCode {
76     if (retried) {
77         return [retriedRequest responseStatusCode];
78     } else {
79         return [super responseStatusCode];
80     }
81 }
82
83 - (NSString *)responseStatusMessage {
84     if (retried) {
85         return [retriedRequest responseStatusMessage];
86     } else {
87         return [super responseStatusMessage];
88     }
89 }
90
91 - (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
92     [super setCompletionBlock:aCompletionBlock];
93     [backupCompletionBlock release];
94     backupCompletionBlock = [aCompletionBlock copy];
95 }
96
97 - (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
98     [super setFailedBlock:aFailedBlock];
99     [backupFailureBlock release];
100     backupFailureBlock = [aFailedBlock copy];
101 }
102
103 #pragma mark -
104 #pragma mark Generic Constructors
105
106 + (void)initialize {
107         if (self == [OpenStackRequest class]) {
108                 accessDetailsLock = [[NSRecursiveLock alloc] init];
109         }
110 }
111
112 + (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
113         OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:url] autorelease];
114     request.account = account;
115         [request setRequestMethod:method];
116         [request addRequestHeader:@"X-Auth-Token" value:[account authToken]];
117     [request addRequestHeader:@"Content-Type" value:@"application/json"];
118     [request setTimeOutSeconds:60];
119     [request setNumberOfTimesToRetryOnTimeout:5];
120     request.retriedCount = 0;
121         return request;
122 }
123
124 + (id)getSharingAccountsRequest:(OpenStackAccount *)account {
125     NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json", account.provider.authEndpointURL]];
126     return [OpenStackRequest request:account method:@"GET" url:url];
127 }
128
129 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
130     NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
131     
132     NSString *urlString = [account.filesURL description];
133     if (account.sharingAccount) {
134         NSRange authUserRange = [urlString rangeOfString:account.username];
135         urlString = [NSString stringWithFormat:@"%@%@", [urlString substringToIndex:authUserRange.location], account.sharingAccount];
136     }
137     
138         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@&now=%@",
139                                        urlString,
140                                        path,
141                                        account.shared ? @"&shared=" : @"",
142                                        now]];
143     
144     return [OpenStackRequest request:account method:method url:url];
145 }
146
147 #pragma mark -
148 #pragma mark Auth Retry
149
150 - (void)authRetrySucceded:(OpenStackRequest *)retryRequest {
151     self.account.authToken = [[retryRequest responseHeaders] objectForKey:@"X-Auth-Token"]; 
152     [self.account persist];
153     
154     // try the original request again!
155     retried = YES;
156     retriedRequest = [self copy];
157
158         [retriedRequest addRequestHeader:@"X-Auth-Token" value:self.account.authToken];    
159     
160     if (backupCompletionBlock) {
161         [retriedRequest setCompletionBlock:^{
162             backupCompletionBlock();
163         }];
164     }
165     if (backupFailureBlock) {
166         [retriedRequest setFailedBlock:^{
167             backupFailureBlock();
168         }];
169     }
170
171     [retriedRequest startSynchronous];     
172 }
173
174 - (void)authRetryFailed:(OpenStackRequest *)retryRequest {
175     // if it fails due to bad connection, try again?
176     NSNotification *notification = [NSNotification notificationWithName:[self.account.manager notificationName:@"authRetryFailed" identifier:0] object:nil userInfo:[NSDictionary dictionaryWithObject:retryRequest forKey:@"request"]];
177     [[NSNotificationCenter defaultCenter] postNotification:notification];
178 }
179
180 #pragma mark -
181 #pragma mark ASIHTTPRequest Overrides
182
183 - (void)failWithError:(NSError *)theError {
184     if (responseStatusCode == 401 && ![url isEqual:account.provider.authEndpointURL]) {
185         // auth is expired, so get a fresh token
186         if (account && ![account.provider isGRNet]) {
187             OpenStackRequest *retryRequest = [OpenStackRequest authenticationRequest:account];
188             retryRequest.delegate = self;
189             retryRequest.didFinishSelector = @selector(authRetrySucceded:);
190             retryRequest.didFailSelector = @selector(authRetryFailed:);
191             [retryRequest startSynchronous];
192         }
193     } else if (responseStatusCode == 503) {        
194         NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
195         [[NSNotificationCenter defaultCenter] postNotification:notification];        
196 //        [super failWithError:theError];
197     } else if (responseStatusCode == 0) {
198         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
199         if (![defaults boolForKey:@"already_failed_on_connection"]) {
200             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
201             [alert show];
202             [alert release];
203         }
204         [defaults setBool:YES forKey:@"already_failed_on_connection"];
205         [defaults synchronize];              
206     }
207
208     [super failWithError:theError];
209 }
210
211 #pragma mark - Authentication
212
213 + (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account {
214
215         OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:account.provider.authEndpointURL] autorelease];
216     request.account = account;
217     [request addRequestHeader:@"X-Auth-User" value:account.username];
218     if (account.apiKey) {
219         [request addRequestHeader:@"X-Auth-Key" value:account.apiKey];
220     } else {
221         [request addRequestHeader:@"X-Auth-Key" value:@""];
222     }
223
224         return request;
225 }
226
227 #pragma mark -
228 #pragma mark Object Storage Requests
229
230 + (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account {
231     return [OpenStackRequest filesRequest:account method:@"HEAD" path:@""];
232 }
233
234 + (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account {
235     return [OpenStackRequest filesRequest:account method:@"GET" path:@""];
236 }
237
238 - (NSMutableDictionary *)containers {
239     SBJSON *parser = [[SBJSON alloc] init];
240     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
241     NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
242     
243     for (int i = 0; i < [jsonObjects count]; i++) {
244         NSDictionary *dict = [jsonObjects objectAtIndex:i];
245         Container *container = [Container fromJSON:dict];
246         [objects setObject:container forKey:container.name];
247     }
248     
249     [parser release];
250     return objects;
251 }
252
253 + (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {    
254     return [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
255 }
256
257 + (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
258     return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
259 }
260
261 + (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
262     return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];    
263 }
264
265 - (NSMutableDictionary *)objects {
266     SBJSON *parser = [[SBJSON alloc] init];
267     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
268
269     NSMutableDictionary *objects = [[[NSMutableDictionary alloc] initWithCapacity:[jsonObjects count]] autorelease];
270     
271     for (int i = 0; i < [jsonObjects count]; i++) {
272         NSDictionary *dict = [jsonObjects objectAtIndex:i];
273         StorageObject *object = [StorageObject fromJSON:dict];
274         [objects setObject:object forKey:object.name];
275     }
276     
277     [parser release];
278     return objects;
279 }
280
281 + (OpenStackRequest *)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container {
282     return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@",[NSString encodeToPercentEscape:container.name]]];    
283 }
284
285 + (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
286     NSString *objectFullPath = object.fullPath;
287     if ([objectFullPath hasPrefix:@"/"])
288         objectFullPath = [objectFullPath substringFromIndex:1];
289     return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
290 }
291
292 + (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account
293                                  container:(Container *)container
294                                     object:(StorageObject *)object
295                                    version:(NSString *)version {
296     OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:account container:container object:object];
297     request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
298     return request;
299 }
300
301 + (OpenStackRequest *)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
302     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
303     
304     request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list",request.url.description]];
305     return request;
306 }
307
308 + (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
309     return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
310 }
311
312 + (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account
313                              container:(Container *)container
314                                 object:(StorageObject *)object
315                                version:(NSString *)version {
316     OpenStackRequest *request = [OpenStackRequest getObjectRequest:account container:container object:object];
317     if (version) {
318         request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
319     }
320     return request;
321 }
322
323 + (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
324     NSString *fullPath = object.fullPath;
325     if ([fullPath characterAtIndex:0] == '/') {
326         fullPath = [fullPath substringFromIndex:1];
327     }
328         
329     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
330     
331     if (object.sharing)
332         [request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"];
333     
334     NSString *metadataKeyHeaderPrefix;
335     if ([fullPath length] == 0)
336         metadataKeyHeaderPrefix = @"X-Container-Meta-";
337     else
338         metadataKeyHeaderPrefix = @"X-Object-Meta-";
339     
340     for (NSString *metadataKey in object.metadata) {
341         NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
342         metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
343         NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
344         [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
345     }
346
347         [request setPostBody:[NSMutableData dataWithData:object.data]];
348     [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];    
349         return request;
350 }
351
352 + (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
353     NSString *fullPath = object.fullPath;
354     if ([fullPath length] != 0 && [fullPath characterAtIndex:0] == '/') {
355         fullPath = [fullPath substringFromIndex:1];
356     }
357
358     NSString *metadataKeyHeaderPrefix;
359     if ([fullPath length] == 0)
360         metadataKeyHeaderPrefix = @"X-Container-Meta-";
361     else
362         metadataKeyHeaderPrefix = @"X-Object-Meta-";
363         
364     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; 
365
366     for (NSString *metadataKey in object.metadata) {
367         NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
368         metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
369         NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
370         [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
371     }
372     if (!account.sharingAccount) {
373         NSString *objectIsPublic = ([object.publicURI length] > 0) ? @"true" : @"false";
374         [request.requestHeaders setObject:objectIsPublic forKey:@"X-Object-Public"];
375     
376         if (object.sharing) {
377             NSString *urlEncodedSharingString = [NSString encodeToPercentEscape:object.sharing];
378             [request.requestHeaders setObject:urlEncodedSharingString forKey:@"X-Object-Sharing"];
379         }
380     }
381     return request;
382 }
383
384 + (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
385     if ([object.fullPath characterAtIndex:0] == '/') {
386         return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
387     } else {
388         return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
389     }
390 }
391
392 + (OpenStackRequest *)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container {
393     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
394     
395     [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
396     [request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"];
397     
398     return request;
399 }
400
401 + (OpenStackRequest *)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo {
402     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:@""];
403     
404     NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
405     for (NSString *groupName in groups) {
406         NSString *group = [NSString encodeToPercentEscape:[groups objectForKey:groupName]];
407         groupName = [NSString encodeToPercentEscape:groupName];
408         [request.requestHeaders setObject:group forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]];
409     }
410     if ([groups count] == 0)
411         [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
412     
413     NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"];
414     for (NSString *metadataKey in accountMetadata) {
415         NSString *metadataValue = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
416         metadataKey = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
417         [request.requestHeaders setObject:metadataValue forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]];
418     }
419     
420     return request;
421 }
422
423 #pragma mark -
424 #pragma mark Memory Management
425
426 - (void)releaseBackupBlocksOnMainThread {
427         NSMutableArray *blocks = [NSMutableArray array];
428         if (backupCompletionBlock) {
429                 [blocks addObject:backupCompletionBlock];
430                 [backupCompletionBlock release];
431                 backupCompletionBlock = nil;
432         }
433         if (backupFailureBlock) {
434                 [blocks addObject:backupFailureBlock];
435                 [backupFailureBlock release];
436                 backupFailureBlock = nil;
437         }
438         [[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
439 }
440
441 // Always called on main thread
442 + (void)releaseBackupBlocks:(NSArray *)blocks {
443         // Blocks will be released when this method exits
444 }
445
446 - (void)dealloc {
447     [account release];
448     [errorAlerter release];
449     [self releaseBackupBlocksOnMainThread];
450     [super dealloc];
451 }
452
453 @end