Added my shared/others shared functionality.
[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 "Server.h"
13 #import "Image.h"
14 #import "Flavor.h"
15 #import "APILogger.h"
16 #import "JSON.h"
17 #import "RateLimit.h"
18 #import "Container.h"
19 #import "StorageObject.h"
20 #import "Folder.h"
21 #import "BackupSchedule.h"
22 #import "AccountManager.h"
23 #import "GetFlavorsRequest.h"
24 #import "APICallback.h"
25 #import "APILogEntry.h"
26
27
28 static NSRecursiveLock *accessDetailsLock = nil;
29
30 @implementation OpenStackRequest
31
32 @synthesize account, callback, retriedCount;
33
34 - (BOOL)isSuccess {
35         return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
36 }
37
38 - (void)notify:(NSString *)name {
39     NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
40     NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo];
41     [[NSNotificationCenter defaultCenter] postNotification:notification];
42 }
43
44 - (void)notify {
45     NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description]];
46     NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description], self.callback.uuid];
47     
48     NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
49
50     NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
51     [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
52
53     NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
54     [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
55     
56 }
57
58 - (NSString *)responseString {
59     if (retried) {
60         return [retriedRequest responseString];
61     } else {
62         return [super responseString];
63     }
64 }
65
66 - (NSData *)responseData {
67     if (retried) {
68         return [retriedRequest responseData];
69     } else {
70         return [super responseData];
71     }
72 }
73
74 - (NSDictionary *)responseHeaders {
75     if (retried) {
76         return [retriedRequest responseHeaders];
77     } else {
78         return [super responseHeaders];
79     }
80 }
81
82 - (int)responseStatusCode {
83     if (retried) {
84         return [retriedRequest responseStatusCode];
85     } else {
86         return [super responseStatusCode];
87     }
88 }
89
90 - (NSString *)responseStatusMessage {
91     if (retried) {
92         return [retriedRequest responseStatusMessage];
93     } else {
94         return [super responseStatusMessage];
95     }
96 }
97
98 - (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
99     [super setCompletionBlock:aCompletionBlock];
100     [backupCompletionBlock release];
101     backupCompletionBlock = [aCompletionBlock copy];
102 }
103
104 - (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
105     [super setFailedBlock:aFailedBlock];
106     [backupFailureBlock release];
107     backupFailureBlock = [aFailedBlock copy];
108 }
109
110 + (RateLimit *)limitForPath:(NSString *)path verb:(NSString *)verb account:(OpenStackAccount *)anAccount {
111     
112     // find the lowest requests remaining of all the limits
113
114     RateLimit *lowestLimit = nil;
115     
116     for (RateLimit *limit in anAccount.rateLimits) {
117         if ([limit.verb isEqualToString:verb]) {
118             NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:limit.regex options:NSRegularExpressionCaseInsensitive error:nil];
119             NSInteger matches = [regex numberOfMatchesInString:path options:0 range:NSMakeRange(0, [path length])];
120             
121             if (matches > 0) {
122                 if (lowestLimit) {
123                     if (limit.remaining < lowestLimit.remaining) {
124                         lowestLimit = limit;
125                     }
126                 } else {
127                     lowestLimit = limit;
128                 }
129             }
130         }
131     }
132     
133     return lowestLimit;
134 }
135
136 #pragma mark -
137 #pragma mark Generic Constructors
138
139 + (void)initialize {
140         if (self == [OpenStackRequest class]) {
141                 accessDetailsLock = [[NSRecursiveLock alloc] init];
142         }
143 }
144
145 + (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
146         OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:url] autorelease];
147     request.account = account;
148         [request setRequestMethod:method];
149         [request addRequestHeader:@"X-Auth-Token" value:[account authToken]];
150     [request addRequestHeader:@"Content-Type" value:@"application/json"];
151     [request setTimeOutSeconds:60];
152     request.retriedCount = 0;
153         return request;
154 }
155
156 + (id)getSharingAccountsRequest:(OpenStackAccount *)account {
157     NSString *topLevelUrlString = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"GRNetStorageUrlPrefix"];
158     NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json",topLevelUrlString]];
159     return [OpenStackRequest request:account method:@"GET" url:url];
160 }
161
162 + (id)serversRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
163     NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
164         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?now=%@", account.serversURL, path, now]];
165     return [OpenStackRequest request:account method:method url:url];
166 }
167
168 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
169     NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
170     
171     NSString *filesUrl;
172     NSString *urlString = [account.filesURL description];
173     if (account.sharingAccount) {
174         NSRange authUserRange = [urlString rangeOfString:account.username];
175         filesUrl = [NSString stringWithFormat:@"%@%@",
176                     [urlString substringToIndex:authUserRange.location],
177                     account.sharingAccount];
178     } else {
179         filesUrl = urlString;
180         }
181     
182         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@&now=%@",
183                                        filesUrl,
184                                        path,
185                                        account.shared ? @"&shared=" : @"",
186                                        now]];   
187     return [OpenStackRequest request:account method:method url:url];
188 }
189
190 + (id)cdnRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
191     NSString *now = [[[NSDate date] description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
192         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json&now=%@", account.cdnURL, path, now]];
193     return [OpenStackRequest request:account method:method url:url];
194 }
195
196 #pragma mark -
197 #pragma mark Auth Retry
198
199 - (void)authRetrySucceded:(OpenStackRequest *)retryRequest {
200     
201     if ([self isKindOfClass:[GetFlavorsRequest class]]) {
202         NSLog(@"flavor request");
203     }
204     
205     self.account.authToken = [[retryRequest responseHeaders] objectForKey:@"X-Auth-Token"];    
206     [self.account persist];
207     
208     // TODO: make this work for GetServersRequest, etc
209     
210     // try the original request again!
211     retried = YES;
212     retriedRequest = [self copy];
213         [retriedRequest addRequestHeader:@"X-Auth-Token" value:self.account.authToken];    
214     
215     if (backupCompletionBlock) {
216         [retriedRequest setCompletionBlock:^{
217             backupCompletionBlock();
218         }];
219     }
220     if (backupFailureBlock) {
221         [retriedRequest setFailedBlock:^{
222             backupFailureBlock();
223         }];
224     }
225     [retriedRequest startSynchronous];     
226 }
227
228 - (void)authRetryFailed:(OpenStackRequest *)retryRequest {
229     // if it fails due to bad connection, try again?
230     NSLog(@"auth retry failed with status %i", retryRequest.responseStatusCode);
231     NSNotification *notification = [NSNotification notificationWithName:[self.account.manager notificationName:@"authRetryFailed" identifier:0] object:nil userInfo:[NSDictionary dictionaryWithObject:retryRequest forKey:@"request"]];
232     [[NSNotificationCenter defaultCenter] postNotification:notification];
233 }
234
235 #pragma mark -
236 #pragma mark ASIHTTPRequest Overrides
237 // overriding to log API calls
238
239 - (void)requestFinished {
240     //NSLog(@"request finished: %i %@", self.responseStatusCode, self.url);
241     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
242     NSString *loggingLevel = [defaults valueForKey:@"api_logging_level"];    
243     if ([loggingLevel isEqualToString:@"all"] || ([loggingLevel isEqualToString:@"errors"] && ![self isSuccess])) {
244         [APILogger log:self];
245     }
246     
247     [super requestFinished];
248 }
249
250 - (void)failWithError:(NSError *)theError {
251
252     if (responseStatusCode == 401 && ![url isEqual:account.provider.authEndpointURL]) {
253         // auth is expired, so get a fresh token
254         if (account) {
255             OpenStackRequest *retryRequest = [OpenStackRequest authenticationRequest:account];
256             retryRequest.delegate = self;
257             retryRequest.didFinishSelector = @selector(authRetrySucceded:);
258             retryRequest.didFailSelector = @selector(authRetryFailed:);
259             [retryRequest startSynchronous];
260         }
261     } else if (responseStatusCode == 503) {        
262         NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
263         [[NSNotificationCenter defaultCenter] postNotification:notification];        
264         [super failWithError:theError];
265     } else if (responseStatusCode == 0) {
266         NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
267         if (![defaults boolForKey:@"already_failed_on_connection"]) {
268             UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
269             [alert show];
270             [alert release];
271         }
272         [defaults setBool:YES forKey:@"already_failed_on_connection"];
273         [defaults synchronize];              
274     }
275
276     [super failWithError:theError];
277 }
278
279 #pragma mark - Authentication
280
281 + (NSString *)apiVersionForURL:(NSURL *)url {
282     NSArray *components = [[url description] componentsSeparatedByString:@"/"];
283     if ([[components lastObject] isEqualToString:@"2.0"]) {
284         return @"2.0";
285     } else if ([[components lastObject] isEqualToString:@"v1.1"]) {
286         return @"1.1";
287     } else {
288         return @"1.0";
289     }
290 }
291
292 + (void)setupV1AuthForRequest:(OpenStackRequest *)request account:(OpenStackAccount *)account apiVersion:(NSString *)apiVersion {
293     [request addRequestHeader:@"X-Auth-User" value:account.username];
294     if (account.apiKey) {
295         [request addRequestHeader:@"X-Auth-Key" value:account.apiKey];
296     } else {
297         [request addRequestHeader:@"X-Auth-Key" value:@""];
298     }        
299 }
300
301 + (void)setupAuthForRequest:(OpenStackRequest *)request account:(OpenStackAccount *)account apiVersion:(NSString *)apiVersion {
302
303     if ([apiVersion isEqualToString:@"1.0"]) {
304         
305         [OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion];
306         account.apiVersion = @"1.0";
307         
308     } else if ([apiVersion isEqualToString:@"1.1"]) {
309         
310         [OpenStackRequest setupV1AuthForRequest:request account:account apiVersion:apiVersion];
311         if (account.projectId) {
312             [request addRequestHeader:@"X-Auth-Project-Id" value:account.projectId];
313         }
314         account.apiVersion = @"1.1";
315         
316     } else if ([apiVersion isEqualToString:@"2.0"]) {
317         
318         // TODO: support 2.0 auth
319         account.apiVersion = @"1.1";
320     }
321 }
322
323 + (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account {
324
325         OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:account.provider.authEndpointURL] autorelease];
326     request.account = account;
327
328     NSString *apiVersion = [OpenStackRequest apiVersionForURL:account.provider.authEndpointURL];
329     [OpenStackRequest setupAuthForRequest:request account:account apiVersion:apiVersion];
330
331         return request;
332 }
333
334 #pragma mark - Rate Limits
335
336 + (OpenStackRequest *)getLimitsRequest:(OpenStackAccount *)account {
337     return [OpenStackRequest serversRequest:account method:@"GET" path:@"/limits"];
338 }
339
340 - (NSDictionary *)limits {
341     SBJSON *parser = [[SBJSON alloc] init];
342     NSDictionary *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"limits"];    
343     [parser release];
344     return jsonObjects;
345 }
346
347 - (NSArray *)rateLimits {
348     NSArray *jsonObjects = [[self limits] objectForKey:@"rate"];
349     NSMutableArray *rateLimits = [[NSMutableArray alloc] initWithCapacity:[jsonObjects count]];
350
351     for (NSDictionary *dict in jsonObjects) {
352         [rateLimits addObject:[RateLimit fromJSON:dict]];
353     }
354
355     NSArray *result = [NSArray arrayWithArray:rateLimits];
356     
357     [rateLimits release];
358     return result;
359 }
360
361 #pragma mark Collections
362
363 + (OpenStackRequest *)getServersRequest:(OpenStackAccount *)account {
364     return [OpenStackRequest serversRequest:account method:@"GET" path:@"/servers/detail"];
365 }
366
367 - (NSDictionary *)servers {
368     SBJSON *parser = [[SBJSON alloc] init];
369     NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"servers"];
370     NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
371     
372     for (int i = 0; i < [jsonObjects count]; i++) {
373         NSDictionary *dict = [jsonObjects objectAtIndex:i];
374         Server *server = [[Server alloc] initWithJSONDict:dict];
375         [objects setObject:server forKey:server.identifier];
376         [server release];
377     }
378     
379     [parser release];
380     return objects;
381 }
382
383 + (OpenStackRequest *)getServerRequest:(OpenStackAccount *)account serverId:(NSString *)serverId {
384     return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@", serverId]];
385 }
386
387 + (OpenStackRequest *)getImagesRequest:(OpenStackAccount *)account {
388     return [OpenStackRequest serversRequest:account method:@"GET" path:@"/images/detail"];
389 }
390
391 - (NSDictionary *)images {
392     SBJSON *parser = [[SBJSON alloc] init];
393     NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"images"];
394     NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
395     
396     for (int i = 0; i < [jsonObjects count]; i++) {
397         NSDictionary *dict = [jsonObjects objectAtIndex:i];
398         Image *image = [[Image alloc] initWithJSONDict:dict];
399         [objects setObject:image forKey:image.identifier];
400         [image release];
401     }
402     
403     [parser release];
404     return objects;
405 }
406
407 + (OpenStackRequest *)getImageRequest:(OpenStackAccount *)account imageId:(NSString *)imageId {
408     return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/images/%@", imageId]];
409 }
410
411 - (Image *)image {
412     SBJSON *parser = [[SBJSON alloc] init];
413     NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"image"];
414     Image *image = [Image fromJSON:dict];
415     [parser release];
416     return image;
417 }
418
419
420 + (OpenStackRequest *)getFlavorsRequest:(OpenStackAccount *)account {
421     return [OpenStackRequest serversRequest:account method:@"GET" path:@"/flavors/detail"];
422 }
423
424 - (NSDictionary *)flavors {
425     SBJSON *parser = [[SBJSON alloc] init];
426     NSArray *jsonObjects = [[parser objectWithString:[self responseString]] objectForKey:@"flavors"];
427     NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
428
429     for (int i = 0; i < [jsonObjects count]; i++) {
430         NSDictionary *dict = [jsonObjects objectAtIndex:i];
431         Flavor *flavor = [Flavor fromJSON:dict];
432         [objects setObject:flavor forKey:flavor.identifier];
433     }
434     
435     [parser release];
436     return objects;
437 }
438
439 #pragma mark Server Actions
440
441 #define kSoft 0
442 #define kHard 1
443
444 + (OpenStackRequest *)rebootServerRequest:(OpenStackAccount *)account server:(Server *)server type:(NSInteger)type {
445     NSString *body = [NSString stringWithFormat:@"{ \"reboot\": { \"type\": \"%@\" } }", (type == kSoft) ? @"SOFT" : @"HARD"];
446     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];     
447     NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
448     [request setPostBody:[NSMutableData dataWithData:data]];
449     return request;
450 }
451
452 + (OpenStackRequest *)softRebootServerRequest:(OpenStackAccount *)account server:(Server *)server {
453     return [OpenStackRequest rebootServerRequest:account server:server type:kSoft];
454 }
455
456 + (RateLimit *)softRebootServerLimit:(OpenStackAccount *)account server:(Server *)server {
457     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
458 }
459
460 + (OpenStackRequest *)hardRebootServerRequest:(OpenStackAccount *)account server:(Server *)server {
461     return [OpenStackRequest rebootServerRequest:account server:server type:kHard];
462 }
463
464 + (RateLimit *)hardRebootServerLimit:(OpenStackAccount *)account server:(Server *)server {
465     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
466 }
467
468 + (OpenStackRequest *)changeServerAdminPasswordRequest:(OpenStackAccount *)account server:(Server *)server password:(NSString *)password {
469         NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"adminPass\": \"%@\" } }", password];
470     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];     
471         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
472         [request setPostBody:[NSMutableData dataWithData:data]];
473         return request;
474 }
475
476 + (RateLimit *)changeServerAdminPasswordLimit:(OpenStackAccount *)account server:(Server *)server {
477     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account];
478 }
479
480 + (OpenStackRequest *)renameServerRequest:(OpenStackAccount *)account server:(Server *)server name:(NSString *)name {
481         NSString *body = [NSString stringWithFormat:@"{ \"server\": { \"name\": \"%@\" } }", name];
482     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];     
483         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
484         [request setPostBody:[NSMutableData dataWithData:data]];
485         return request;
486 }
487
488 + (RateLimit *)renameServerLimit:(OpenStackAccount *)account server:(Server *)server {
489     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"PUT" account:account];
490 }
491
492 + (OpenStackRequest *)deleteServerRequest:(OpenStackAccount *)account server:(Server *)server {
493     return [OpenStackRequest serversRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/servers/%@", server.identifier]];
494 }
495
496 + (RateLimit *)deleteServerLimit:(OpenStackAccount *)account server:(Server *)server {
497     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@", server.identifier] verb:@"DELETE" account:account];
498 }
499
500 + (OpenStackRequest *)createServerRequest:(OpenStackAccount *)account server:(Server *)server {
501         NSString *body = [server toJSON:account.apiVersion];
502         NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@/servers", account.serversURL]];
503     NSLog(@"create server: %@", body);
504     OpenStackRequest *request = [OpenStackRequest request:account method:@"POST" url:url];    
505         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
506         [request setPostBody:[NSMutableData dataWithData:data]];
507         return request;
508 }
509
510 + (RateLimit *)createServerLimit:(OpenStackAccount *)account {
511     return [OpenStackRequest limitForPath:@"/servers" verb:@"POST" account:account];
512 }
513
514 - (Server *)server {
515     SBJSON *parser = [[SBJSON alloc] init];
516     NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"server"];
517     Server *server = [Server fromJSON:dict];
518     [parser release];
519     return server;
520 }
521
522 + (OpenStackRequest *)resizeServerRequest:(OpenStackAccount *)account server:(Server *)server flavor:(Flavor *)flavor {
523         NSString *body;
524     if ([account.apiVersion isEqualToString:@"1.0"]) {
525         body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorId\": %@ } }", flavor.identifier];
526     } else {
527         body = [NSString stringWithFormat:@"{ \"resize\": { \"flavorRef\": %@ } }", flavor.identifier];
528     }
529     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];     
530         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
531         [request setPostBody:[NSMutableData dataWithData:data]];
532         return request;
533 }
534
535 + (RateLimit *)resizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
536     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
537 }
538
539 + (OpenStackRequest *)confirmResizeServerRequest:(OpenStackAccount *)account server:(Server *)server {
540         NSString *body = @"{ \"confirmResize\": null }";
541     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];     
542         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
543         [request setPostBody:[NSMutableData dataWithData:data]];
544         return request;
545 }
546
547 + (RateLimit *)confirmResizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
548     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
549 }
550
551 + (OpenStackRequest *)revertResizeServerRequest:(OpenStackAccount *)account server:(Server *)server {
552         NSString *body = @"{ \"revertResize\": null }";
553     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];     
554         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
555         [request setPostBody:[NSMutableData dataWithData:data]];
556         return request;
557 }
558
559 + (RateLimit *)revertResizeServerLimit:(OpenStackAccount *)account server:(Server *)server {
560     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
561 }
562
563 + (OpenStackRequest *)rebuildServerRequest:(OpenStackAccount *)account server:(Server *)server image:(Image *)image {
564         NSString *body = [NSString stringWithFormat:@"{ \"rebuild\": { \"imageId\": %@ } }", image.identifier];
565     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/action", server.identifier]];     
566         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
567         [request setPostBody:[NSMutableData dataWithData:data]];
568         return request;
569 }
570
571 + (RateLimit *)rebuildServerLimit:(OpenStackAccount *)account server:(Server *)server {
572     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/action", server.identifier] verb:@"POST" account:account];
573 }
574
575
576 + (OpenStackRequest *)getBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server {    
577     return [OpenStackRequest serversRequest:account method:@"GET" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]];
578 }
579
580 + (RateLimit *)getBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server {
581     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"GET" account:account];
582 }
583
584 + (OpenStackRequest *)updateBackupScheduleRequest:(OpenStackAccount *)account server:(Server *)server {
585         NSString *body = [NSString stringWithFormat:@"{ \"backupSchedule\": { \"enabled\": true, \"weekly\": \"%@\", \"daily\": \"%@\" } }", server.backupSchedule.weekly, server.backupSchedule.daily];
586     OpenStackRequest *request = [OpenStackRequest serversRequest:account method:@"POST" path:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier]];    
587         NSData *data = [body dataUsingEncoding:NSUTF8StringEncoding];
588         [request setPostBody:[NSMutableData dataWithData:data]];
589         return request;
590 }
591
592 + (RateLimit *)updateBackupScheduleLimit:(OpenStackAccount *)account server:(Server *)server {
593     return [OpenStackRequest limitForPath:[NSString stringWithFormat:@"/servers/%@/backup_schedule", server.identifier] verb:@"POST" account:account];
594 }
595
596 - (BackupSchedule *)backupSchedule {
597     SBJSON *parser = [[SBJSON alloc] init];
598     NSDictionary *dict = [[parser objectWithString:[self responseString]] objectForKey:@"backupSchedule"];
599     BackupSchedule *backupSchedule = [BackupSchedule fromJSON:dict];
600     [parser release];
601     return backupSchedule;
602 }
603
604 #pragma mark -
605 #pragma mark Object Storage Requests
606
607 + (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account {
608     return [OpenStackRequest filesRequest:account method:@"HEAD" path:@""];
609 }
610
611 + (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account {
612     return [OpenStackRequest filesRequest:account method:@"GET" path:@""];
613 }
614
615 - (NSMutableDictionary *)containers {
616     SBJSON *parser = [[SBJSON alloc] init];
617     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
618     NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
619     
620     for (int i = 0; i < [jsonObjects count]; i++) {
621         NSDictionary *dict = [jsonObjects objectAtIndex:i];
622         Container *container = [Container fromJSON:dict];
623         [objects setObject:container forKey:container.name];
624     }
625     
626     [parser release];
627     return objects;
628 }
629
630 + (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {    
631     return [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
632 }
633
634 + (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
635     return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
636 }
637
638 + (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
639     return [OpenStackRequest filesRequest:account method:@"GET" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
640 }
641
642 - (NSMutableDictionary *)objects {
643     SBJSON *parser = [[SBJSON alloc] init];
644     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
645     NSMutableDictionary *objects = [[[NSMutableDictionary alloc] initWithCapacity:[jsonObjects count]] autorelease];
646     
647     for (int i = 0; i < [jsonObjects count]; i++) {
648         NSDictionary *dict = [jsonObjects objectAtIndex:i];
649         StorageObject *object = [StorageObject fromJSON:dict];
650         [objects setObject:object forKey:object.name];
651     }
652     
653     [parser release];
654     return objects;
655 }
656
657 + (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
658     return [OpenStackRequest filesRequest:account method:@"HEAD" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
659 }
660
661 + (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
662     return [OpenStackRequest filesRequest:account method:@"GET" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
663 }
664
665 + (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
666     NSString *fullPath = object.fullPath;
667     if ([fullPath characterAtIndex:0] == '/') {
668         fullPath = [fullPath substringFromIndex:1];
669     }
670     
671     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@/%@", container.name, fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];    
672         [request setPostBody:[NSMutableData dataWithData:object.data]];
673     [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];
674         return request;
675 }
676
677 + (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
678     NSString *fullPath = [NSString stringWithString:object.fullPath];
679     if ([fullPath length] && ([fullPath characterAtIndex:0] == '/'))
680         fullPath = [fullPath substringFromIndex:1];
681
682     NSString *metadataKeyHeaderPrefix;
683     if (![fullPath length])
684         metadataKeyHeaderPrefix = @"X-Container-Meta-";
685     else
686         metadataKeyHeaderPrefix = @"X-Object-Meta-";
687         
688     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:[[NSString stringWithFormat:@"/%@/%@", container.name, fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 
689     
690     for (NSString *metadataKey in object.metadata) {
691         NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
692         [request.requestHeaders setObject:[object.metadata objectForKey:metadataKey] forKey:metadataKeyHeader];
693     }
694     if (!account.sharingAccount) {
695         NSString *objectIsPublic = ([object.publicURI length]) ? @"true" : @"false";
696         [request.requestHeaders setObject:objectIsPublic forKey:@"X-Object-Public"];
697     
698         if (object.sharing)
699             [request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"];
700     }
701
702     return request;
703 }
704
705 + (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
706     if ([object.fullPath characterAtIndex:0] == '/') {
707         return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
708     } else {
709         return [OpenStackRequest filesRequest:account method:@"DELETE" path:[[NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
710     }
711 }
712
713 + (OpenStackRequest *)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container
714 {
715     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[[NSString stringWithFormat:@"/%@", container.name] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]; 
716     
717     [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
718     [request.requestHeaders setObject:[NSString stringWithFormat:@"%lu", container.quota] forKey:@"X-Container-Policy-Quota"];
719     
720     return request;
721 }
722
723 + (OpenStackRequest *)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo
724 {
725     OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:@""];
726     
727     NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
728     for (NSString *groupName in groups) {
729         [request.requestHeaders setObject:[groups objectForKey:groupName] forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]];
730     }
731     if ([groups count] == 0)
732         [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
733     
734     NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"];
735     for (NSString *metadataKey in accountMetadata) {
736         [request.requestHeaders setObject:[accountMetadata objectForKey:metadataKey] forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]];
737     }
738     
739     return request;
740 }
741
742 #pragma mark -
743 #pragma mark Memory Management
744
745 - (void)releaseBackupBlocksOnMainThread {
746         NSMutableArray *blocks = [NSMutableArray array];
747         if (backupCompletionBlock) {
748                 [blocks addObject:backupCompletionBlock];
749                 [backupCompletionBlock release];
750                 backupCompletionBlock = nil;
751         }
752         if (backupFailureBlock) {
753                 [blocks addObject:backupFailureBlock];
754                 [backupFailureBlock release];
755                 backupFailureBlock = nil;
756         }
757         [[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
758 }
759
760 // Always called on main thread
761 + (void)releaseBackupBlocks:(NSArray *)blocks {
762         // Blocks will be released when this method exits
763 }
764
765 - (void)dealloc {
766     [account release];
767     [self releaseBackupBlocksOnMainThread];
768     [super dealloc];
769 }
770
771 @end