root / Classes / OpenStackRequest.m @ c91b5b28
History | View | Annotate | Download (17.5 kB)
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 |
@implementation OpenStackRequest |
22 |
|
23 |
@synthesize account, callback, errorAlerter; |
24 |
|
25 |
#pragma mark - Constructors |
26 |
#pragma mark Generic |
27 |
|
28 |
+ (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url { |
29 |
OpenStackRequest *request = [[[self alloc] initWithURL:url] autorelease]; |
30 |
request.account = account; |
31 |
request.requestMethod = method; |
32 |
[request addRequestHeader:@"X-Auth-Token" value:account.authToken]; |
33 |
[request addRequestHeader:@"Content-Type" value:@"application/json"]; |
34 |
request.timeOutSeconds = 60; |
35 |
request.numberOfTimesToRetryOnTimeout = 5; |
36 |
return request; |
37 |
} |
38 |
|
39 |
+ (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path { |
40 |
NSString *urlString = [account.filesURL description]; |
41 |
if (account.sharingAccount) { |
42 |
urlString = [NSString stringWithFormat:@"%@%@", |
43 |
[urlString substringToIndex:[urlString rangeOfString:account.username].location], |
44 |
account.sharingAccount]; |
45 |
} |
46 |
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@", |
47 |
urlString, |
48 |
path, |
49 |
(account.shared ? @"&shared=" : @"")]]; |
50 |
return [self request:account method:method url:url]; |
51 |
} |
52 |
|
53 |
#pragma mark User Catalog |
54 |
+ (id)userCatalogRequest:(OpenStackAccount *)account displaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs { |
55 |
OpenStackRequest *request = [self request:account method:@"POST" url:account.provider.userCatalogURL]; |
56 |
NSMutableString *dataString = [NSMutableString stringWithString:@"{\"displaynames\":["]; |
57 |
if (displaynames) { |
58 |
for (NSUInteger index = 0 ; index < displaynames.count ; index++) { |
59 |
[dataString appendFormat:@"\"%@\"%@", [displaynames objectAtIndex:index], ((index == displaynames.count - 1) ? @"" : @",")]; |
60 |
} |
61 |
} |
62 |
[dataString appendFormat:@"],\"uuids\":["]; |
63 |
if (UUIDs) { |
64 |
for (NSUInteger index = 0 ; index < UUIDs.count ; index++) { |
65 |
[dataString appendFormat:@"\"%@\"%@", [UUIDs objectAtIndex:index], ((index == UUIDs.count - 1) ? @"" : @",")]; |
66 |
} |
67 |
} |
68 |
[dataString appendFormat:@"]}"]; |
69 |
[request appendPostData:[dataString dataUsingEncoding:NSUTF8StringEncoding]]; |
70 |
return request; |
71 |
} |
72 |
|
73 |
- (NSDictionary *)catalogs { |
74 |
SBJSON *parser = [[[SBJSON alloc] init] autorelease]; |
75 |
NSDictionary *catalogs = [parser objectWithString:[self responseString]]; |
76 |
return catalogs; |
77 |
} |
78 |
|
79 |
- (NSDictionary *)displaynameCatalog { |
80 |
return [[self catalogs] objectForKey:@"displayname_catalog"]; |
81 |
} |
82 |
|
83 |
- (NSDictionary *)UUIDCatalog { |
84 |
return [[self catalogs] objectForKey:@"uuid_catalog"]; |
85 |
} |
86 |
|
87 |
#pragma mark Top |
88 |
|
89 |
+ (id)authenticationRequest:(OpenStackAccount *)account { |
90 |
OpenStackRequest *request = [[[self alloc] initWithURL:account.provider.authEndpointURL] autorelease]; |
91 |
request.account = account; |
92 |
request.requestMethod = @"GET"; |
93 |
[request addRequestHeader:@"X-Auth-User" value:account.username]; |
94 |
[request addRequestHeader:@"X-Auth-Token" value:(account.authToken ? account.authToken : @"")]; |
95 |
request.timeOutSeconds = 60; |
96 |
request.numberOfTimesToRetryOnTimeout = 5; |
97 |
return request; |
98 |
} |
99 |
|
100 |
+ (id)getSharingAccountsRequest:(OpenStackAccount *)account { |
101 |
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json", account.provider.authEndpointURL]]; |
102 |
return [self request:account method:@"GET" url:url]; |
103 |
} |
104 |
|
105 |
- (NSArray *)sharingAccounts { |
106 |
SBJSON *parser = [[[SBJSON alloc] init] autorelease]; |
107 |
NSArray *sharingAccounts = [parser objectWithString:[self responseString]]; |
108 |
return sharingAccounts; |
109 |
} |
110 |
|
111 |
#pragma mark Account |
112 |
|
113 |
+ (id)getStorageAccountInfoRequest:(OpenStackAccount *)account { |
114 |
return [self filesRequest:account method:@"HEAD" path:@""]; |
115 |
} |
116 |
|
117 |
+ (id)getContainersRequest:(OpenStackAccount *)account { |
118 |
return [self filesRequest:account method:@"GET" path:@""]; |
119 |
} |
120 |
|
121 |
- (NSMutableDictionary *)containers { |
122 |
SBJSON *parser = [[[SBJSON alloc] init] autorelease]; |
123 |
NSArray *jsonObjects = [parser objectWithString:[self responseString]]; |
124 |
NSMutableDictionary *containers = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]]; |
125 |
for (NSDictionary *dict in jsonObjects) { |
126 |
Container *container = [Container fromJSON:dict]; |
127 |
[containers setObject:container forKey:container.name]; |
128 |
} |
129 |
return containers; |
130 |
} |
131 |
|
132 |
+ (id)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo { |
133 |
OpenStackRequest *request = [self filesRequest:account method:@"POST" path:@""]; |
134 |
|
135 |
NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"]; |
136 |
for (NSString *groupName in groups) { |
137 |
NSString *group = [NSString encodeToPercentEscape:[groups objectForKey:groupName]]; |
138 |
groupName = [NSString encodeToPercentEscape:groupName]; |
139 |
[request.requestHeaders setObject:group forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]]; |
140 |
} |
141 |
if ([groups count] == 0) |
142 |
[request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"]; |
143 |
|
144 |
NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"]; |
145 |
for (NSString *metadataKey in accountMetadata) { |
146 |
NSString *metadataValue = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]]; |
147 |
metadataKey = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]]; |
148 |
[request.requestHeaders setObject:metadataValue forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]]; |
149 |
} |
150 |
|
151 |
return request; |
152 |
} |
153 |
|
154 |
#pragma mark Container |
155 |
|
156 |
+ (id)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container { |
157 |
return [self filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
158 |
} |
159 |
|
160 |
+ (id)createContainerRequest:(OpenStackAccount *)account container:(Container *)container { |
161 |
return [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
162 |
} |
163 |
|
164 |
+ (id)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container { |
165 |
return [self filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
166 |
} |
167 |
|
168 |
+ (id)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container { |
169 |
return [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
170 |
} |
171 |
|
172 |
- (NSMutableDictionary *)objects { |
173 |
SBJSON *parser = [[[SBJSON alloc] init] autorelease]; |
174 |
NSArray *jsonObjects = [parser objectWithString:[self responseString]]; |
175 |
NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]]; |
176 |
for (NSDictionary *dict in jsonObjects) { |
177 |
StorageObject *object = [StorageObject fromJSON:dict]; |
178 |
[objects setObject:object forKey:object.name]; |
179 |
} |
180 |
return objects; |
181 |
} |
182 |
|
183 |
+ (id)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container { |
184 |
OpenStackRequest *request = [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]]; |
185 |
[request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"]; |
186 |
[request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"]; |
187 |
return request; |
188 |
} |
189 |
|
190 |
#pragma mark Storage Object |
191 |
|
192 |
+ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
193 |
NSString *objectFullPath = object.fullPath; |
194 |
if ([objectFullPath hasPrefix:@"/"]) |
195 |
objectFullPath = [objectFullPath substringFromIndex:1]; |
196 |
return [self filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
197 |
} |
198 |
|
199 |
+ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version { |
200 |
OpenStackRequest *request = [self getObjectInfoRequest:account container:container object:object]; |
201 |
request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]]; |
202 |
return request; |
203 |
} |
204 |
|
205 |
+ (id)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
206 |
OpenStackRequest *request = [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
207 |
request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list", request.url.description]]; |
208 |
return request; |
209 |
} |
210 |
|
211 |
- (NSMutableArray *)versions { |
212 |
SBJSON *parser = [[[SBJSON alloc] init] autorelease]; |
213 |
NSArray *jsonVersions = [[parser objectWithString:[self responseString]] objectForKey:@"versions"]; |
214 |
NSMutableArray *versions = [NSMutableArray arrayWithCapacity:[jsonVersions count]]; |
215 |
for (NSArray *jsonVersion in jsonVersions) { |
216 |
[versions addObject:[NSDictionary dictionaryWithObjectsAndKeys: |
217 |
[jsonVersion objectAtIndex:0], @"versionID", |
218 |
[NSDate dateWithTimeIntervalSince1970:[[jsonVersion objectAtIndex:1] doubleValue]], @"versionDate", |
219 |
nil]]; |
220 |
} |
221 |
return versions; |
222 |
} |
223 |
|
224 |
|
225 |
+ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
226 |
return [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
227 |
} |
228 |
|
229 |
+ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version { |
230 |
OpenStackRequest *request = [self getObjectRequest:account container:container object:object]; |
231 |
if (version) |
232 |
request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]]; |
233 |
return request; |
234 |
} |
235 |
|
236 |
+ (id)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
237 |
NSString *objectFullPath = object.fullPath; |
238 |
if ([objectFullPath hasPrefix:@"/"]) |
239 |
objectFullPath = [objectFullPath substringFromIndex:1]; |
240 |
OpenStackRequest *request = [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
241 |
|
242 |
if (object.sharing) |
243 |
[request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"]; |
244 |
|
245 |
NSString *metadataKeyHeaderPrefix; |
246 |
if ([objectFullPath length] == 0) |
247 |
metadataKeyHeaderPrefix = @"X-Container-Meta-"; |
248 |
else |
249 |
metadataKeyHeaderPrefix = @"X-Object-Meta-"; |
250 |
for (NSString *metadataKey in object.metadata) { |
251 |
NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; |
252 |
metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader]; |
253 |
NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]]; |
254 |
[request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader]; |
255 |
} |
256 |
|
257 |
[request setPostBody:[NSMutableData dataWithData:object.data]]; |
258 |
[request.requestHeaders setObject:object.contentType forKey:@"Content-Type"]; |
259 |
return request; |
260 |
} |
261 |
|
262 |
+ (id)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
263 |
NSString *objectFullPath = object.fullPath; |
264 |
if ([objectFullPath hasPrefix:@"/"]) |
265 |
objectFullPath = [objectFullPath substringFromIndex:1]; |
266 |
OpenStackRequest *request = [self filesRequest:account method:@"POST" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
267 |
|
268 |
NSString *metadataKeyHeaderPrefix; |
269 |
if ([objectFullPath length] == 0) |
270 |
metadataKeyHeaderPrefix = @"X-Container-Meta-"; |
271 |
else |
272 |
metadataKeyHeaderPrefix = @"X-Object-Meta-"; |
273 |
for (NSString *metadataKey in object.metadata) { |
274 |
NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; |
275 |
metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader]; |
276 |
NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]]; |
277 |
[request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader]; |
278 |
} |
279 |
|
280 |
if (!account.sharingAccount) { |
281 |
[request.requestHeaders setObject:([object.publicURI length] ? @"true" : @"false") forKey:@"X-Object-Public"]; |
282 |
if (object.sharing) { |
283 |
[request.requestHeaders setObject:[NSString encodeToPercentEscape:object.sharing] forKey:@"X-Object-Sharing"]; |
284 |
} |
285 |
} |
286 |
return request; |
287 |
} |
288 |
|
289 |
+ (id)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object { |
290 |
NSString *objectFullPath = object.fullPath; |
291 |
if ([objectFullPath hasPrefix:@"/"]) |
292 |
objectFullPath = [objectFullPath substringFromIndex:1]; |
293 |
return [self filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; |
294 |
} |
295 |
|
296 |
#pragma mark - ASIHTTPRequest Overrides |
297 |
|
298 |
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock { |
299 |
[super setCompletionBlock:aCompletionBlock]; |
300 |
[backupCompletionBlock release]; |
301 |
backupCompletionBlock = [aCompletionBlock copy]; |
302 |
} |
303 |
|
304 |
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock { |
305 |
[super setFailedBlock:aFailedBlock]; |
306 |
[backupFailureBlock release]; |
307 |
backupFailureBlock = [aFailedBlock copy]; |
308 |
} |
309 |
|
310 |
- (void)failWithError:(NSError *)theError { |
311 |
if (responseStatusCode == 503) { |
312 |
NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil]; |
313 |
[[NSNotificationCenter defaultCenter] postNotification:notification]; |
314 |
} else if (responseStatusCode == 0) { |
315 |
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; |
316 |
if (![defaults boolForKey:@"already_failed_on_connection"]) { |
317 |
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil]; |
318 |
[alert show]; |
319 |
[alert release]; |
320 |
} |
321 |
[defaults setBool:YES forKey:@"already_failed_on_connection"]; |
322 |
[defaults synchronize]; |
323 |
} |
324 |
|
325 |
[super failWithError:theError]; |
326 |
} |
327 |
|
328 |
#pragma mark - Actions |
329 |
|
330 |
- (BOOL)isSuccess { |
331 |
return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299); |
332 |
} |
333 |
|
334 |
#pragma mark - Notifications |
335 |
|
336 |
- (void)notify:(NSString *)name { |
337 |
NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"]; |
338 |
NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo]; |
339 |
[[NSNotificationCenter defaultCenter] postNotification:notification]; |
340 |
} |
341 |
|
342 |
- (void)notify { |
343 |
NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description]]; |
344 |
NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description], self.callback.uuid]; |
345 |
|
346 |
NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"]; |
347 |
|
348 |
NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo]; |
349 |
[[NSNotificationCenter defaultCenter] postNotification:observeNotification]; |
350 |
|
351 |
NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo]; |
352 |
[[NSNotificationCenter defaultCenter] postNotification:callbackNotification]; |
353 |
|
354 |
} |
355 |
|
356 |
#pragma mark - Memory Management |
357 |
|
358 |
- (void)releaseBackupBlocksOnMainThread { |
359 |
NSMutableArray *blocks = [NSMutableArray array]; |
360 |
if (backupCompletionBlock) { |
361 |
[blocks addObject:backupCompletionBlock]; |
362 |
[backupCompletionBlock release]; |
363 |
backupCompletionBlock = nil; |
364 |
} |
365 |
if (backupFailureBlock) { |
366 |
[blocks addObject:backupFailureBlock]; |
367 |
[backupFailureBlock release]; |
368 |
backupFailureBlock = nil; |
369 |
} |
370 |
[[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]]; |
371 |
} |
372 |
|
373 |
// Always called on main thread |
374 |
+ (void)releaseBackupBlocks:(NSArray *)blocks { |
375 |
// Blocks will be released when this method exits |
376 |
} |
377 |
|
378 |
- (void)dealloc { |
379 |
[account release]; |
380 |
[errorAlerter release]; |
381 |
[self releaseBackupBlocksOnMainThread]; |
382 |
[super dealloc]; |
383 |
} |
384 |
|
385 |
@end |