Clean and improve code
[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 *urlString = [account.filesURL description];
131     if (account.sharingAccount) {
132         NSRange authUserRange = [urlString rangeOfString:account.username];
133         urlString = [NSString stringWithFormat:@"%@%@", [urlString substringToIndex:authUserRange.location], account.sharingAccount];
134     }
135     
136         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@",
137                                        urlString,
138                                        path,
139                                        account.shared ? @"&shared=" : @""]];
140     
141     return [OpenStackRequest request:account method:method url:url];
142 }
143
144 #pragma mark -
145 #pragma mark Auth Retry
146
147 - (void)authRetrySucceded:(OpenStackRequest *)retryRequest {
148     self.account.authToken = [[retryRequest responseHeaders] objectForKey:@"X-Auth-Token"]; 
149     [self.account persist];
150     
151     // try the original request again!
152     retried = YES;
153     retriedRequest = [self copy];
154
155         [retriedRequest addRequestHeader:@"X-Auth-Token" value:self.account.authToken];    
156     
157     if (backupCompletionBlock) {
158         [retriedRequest setCompletionBlock:^{
159             backupCompletionBlock();
160         }];
161     }
162     if (backupFailureBlock) {
163         [retriedRequest setFailedBlock:^{
164             backupFailureBlock();
165         }];
166     }
167
168     [retriedRequest startSynchronous];     
169 }
170
171 - (void)authRetryFailed:(OpenStackRequest *)retryRequest {
172     // if it fails due to bad connection, try again?
173     NSNotification *notification = [NSNotification notificationWithName:[self.account.manager notificationName:@"authRetryFailed" identifier:0] object:nil userInfo:[NSDictionary dictionaryWithObject:retryRequest forKey:@"request"]];
174     [[NSNotificationCenter defaultCenter] postNotification:notification];
175 }
176
177 #pragma mark -
178 #pragma mark ASIHTTPRequest Overrides
179
180 - (void)failWithError:(NSError *)theError {
181     if (responseStatusCode == 401 && ![url isEqual:account.provider.authEndpointURL]) {
182         // auth is expired, so get a fresh token
183         if (account && ![account.provider isGRNet]) {
184             OpenStackRequest *retryRequest = [OpenStackRequest authenticationRequest:account];
185             retryRequest.delegate = self;
186             retryRequest.didFinishSelector = @selector(authRetrySucceded:);
187             retryRequest.didFailSelector = @selector(authRetryFailed:);
188             [retryRequest startSynchronous];
189         }
190     } else if (responseStatusCode == 503) {        
191         NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
192         [[NSNotificationCenter defaultCenter] postNotification:notification];        
193 //        [super failWithError:theError];
194     } else if (responseStatusCode == 0) {
195         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
196         if (![defaults boolForKey:@"already_failed_on_connection"]) {
197             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
198             [alert show];
199             [alert release];
200         }
201         [defaults setBool:YES forKey:@"already_failed_on_connection"];
202         [defaults synchronize];              
203     }
204
205     [super failWithError:theError];
206 }
207
208 #pragma mark - Authentication
209
210 + (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account {
211
212         OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:account.provider.authEndpointURL] autorelease];
213     request.account = account;
214     [request addRequestHeader:@"X-Auth-User" value:account.username];
215     if (account.authToken) {
216         [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
217     } else {
218         [request addRequestHeader:@"X-Auth-Token" value:@""];
219     }
220
221         return request;
222 }
223
224 #pragma mark -
225 #pragma mark Object Storage Requests
226
227 + (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account {
228     return [OpenStackRequest filesRequest:account method:@"HEAD" path:@""];
229 }
230
231 + (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account {
232     return [OpenStackRequest filesRequest:account method:@"GET" path:@""];
233 }
234
235 - (NSMutableDictionary *)containers {
236     SBJSON *parser = [[SBJSON alloc] init];
237     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
238     NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
239     
240     for (int i = 0; i < [jsonObjects count]; i++) {
241         NSDictionary *dict = [jsonObjects objectAtIndex:i];
242         Container *container = [Container fromJSON:dict];
243         [objects setObject:container forKey:container.name];
244     }
245     
246     [parser release];
247     return objects;
248 }
249
250 + (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {    
251     return [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
252 }
253
254 + (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
255     return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
256 }
257
258 + (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
259     return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];    
260 }
261
262 - (NSMutableDictionary *)objects {
263     SBJSON *parser = [[SBJSON alloc] init];
264     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
265
266     NSMutableDictionary *objects = [[[NSMutableDictionary alloc] initWithCapacity:[jsonObjects count]] autorelease];
267     
268     for (int i = 0; i < [jsonObjects count]; i++) {
269         NSDictionary *dict = [jsonObjects objectAtIndex:i];
270         StorageObject *object = [StorageObject fromJSON:dict];
271         [objects setObject:object forKey:object.name];
272     }
273     
274     [parser release];
275     return objects;
276 }
277
278 + (OpenStackRequest *)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container {
279     return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@",[NSString encodeToPercentEscape:container.name]]];    
280 }
281
282 + (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
283     NSString *objectFullPath = object.fullPath;
284     if ([objectFullPath hasPrefix:@"/"])
285         objectFullPath = [objectFullPath substringFromIndex:1];
286     return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
287 }
288
289 + (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account
290                                  container:(Container *)container
291                                     object:(StorageObject *)object
292                                    version:(NSString *)version {
293     OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:account container:container object:object];
294     request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
295     return request;
296 }
297
298 + (OpenStackRequest *)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
299     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
300     
301     request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list",request.url.description]];
302     return request;
303 }
304
305 + (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
306     return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
307 }
308
309 + (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account
310                              container:(Container *)container
311                                 object:(StorageObject *)object
312                                version:(NSString *)version {
313     OpenStackRequest *request = [OpenStackRequest getObjectRequest:account container:container object:object];
314     if (version) {
315         request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
316     }
317     return request;
318 }
319
320 + (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
321     NSString *fullPath = object.fullPath;
322     if ([fullPath characterAtIndex:0] == '/') {
323         fullPath = [fullPath substringFromIndex:1];
324     }
325         
326     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
327     
328     if (object.sharing)
329         [request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"];
330     
331     NSString *metadataKeyHeaderPrefix;
332     if ([fullPath length] == 0)
333         metadataKeyHeaderPrefix = @"X-Container-Meta-";
334     else
335         metadataKeyHeaderPrefix = @"X-Object-Meta-";
336     
337     for (NSString *metadataKey in object.metadata) {
338         NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
339         metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
340         NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
341         [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
342     }
343
344         [request setPostBody:[NSMutableData dataWithData:object.data]];
345     [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];    
346         return request;
347 }
348
349 + (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
350     NSString *fullPath = object.fullPath;
351     if ([fullPath length] != 0 && [fullPath characterAtIndex:0] == '/') {
352         fullPath = [fullPath substringFromIndex:1];
353     }
354
355     NSString *metadataKeyHeaderPrefix;
356     if ([fullPath length] == 0)
357         metadataKeyHeaderPrefix = @"X-Container-Meta-";
358     else
359         metadataKeyHeaderPrefix = @"X-Object-Meta-";
360         
361     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; 
362
363     for (NSString *metadataKey in object.metadata) {
364         NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
365         metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
366         NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
367         [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
368     }
369     if (!account.sharingAccount) {
370         NSString *objectIsPublic = ([object.publicURI length] > 0) ? @"true" : @"false";
371         [request.requestHeaders setObject:objectIsPublic forKey:@"X-Object-Public"];
372     
373         if (object.sharing) {
374             NSString *urlEncodedSharingString = [NSString encodeToPercentEscape:object.sharing];
375             [request.requestHeaders setObject:urlEncodedSharingString forKey:@"X-Object-Sharing"];
376         }
377     }
378     return request;
379 }
380
381 + (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
382     if ([object.fullPath characterAtIndex:0] == '/') {
383         return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
384     } else {
385         return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
386     }
387 }
388
389 + (OpenStackRequest *)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container {
390     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
391     
392     [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
393     [request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"];
394     
395     return request;
396 }
397
398 + (OpenStackRequest *)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo {
399     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:@""];
400     
401     NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
402     for (NSString *groupName in groups) {
403         NSString *group = [NSString encodeToPercentEscape:[groups objectForKey:groupName]];
404         groupName = [NSString encodeToPercentEscape:groupName];
405         [request.requestHeaders setObject:group forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]];
406     }
407     if ([groups count] == 0)
408         [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
409     
410     NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"];
411     for (NSString *metadataKey in accountMetadata) {
412         NSString *metadataValue = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
413         metadataKey = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
414         [request.requestHeaders setObject:metadataValue forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]];
415     }
416     
417     return request;
418 }
419
420 #pragma mark -
421 #pragma mark Memory Management
422
423 - (void)releaseBackupBlocksOnMainThread {
424         NSMutableArray *blocks = [NSMutableArray array];
425         if (backupCompletionBlock) {
426                 [blocks addObject:backupCompletionBlock];
427                 [backupCompletionBlock release];
428                 backupCompletionBlock = nil;
429         }
430         if (backupFailureBlock) {
431                 [blocks addObject:backupFailureBlock];
432                 [backupFailureBlock release];
433                 backupFailureBlock = nil;
434         }
435         [[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
436 }
437
438 // Always called on main thread
439 + (void)releaseBackupBlocks:(NSArray *)blocks {
440         // Blocks will be released when this method exits
441 }
442
443 - (void)dealloc {
444     [account release];
445     [errorAlerter release];
446     [self releaseBackupBlocksOnMainThread];
447     [super dealloc];
448 }
449
450 @end