Statistics
| Branch: | Tag: | Revision:

root / Classes / OpenStackRequest.m @ ef74c42f

History | View | Annotate | Download (21.2 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, retryWithUpdatedURL, followUpSelectorString, notificationURL;
24

    
25
#pragma mark - Constructors
26
#pragma mark Generic
27

    
28
+ (id)requestWithoutToken:(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:@"Content-Type" value:@"application/json"];
33
    request.timeOutSeconds = 60;
34
    request.numberOfTimesToRetryOnTimeout = 5;
35
    request.validatesSecureCertificate = !account.ignoreSSLErrors;
36
    request.retryWithUpdatedURL = (account.provider.manual ? NO : YES);
37
	return request;
38
}
39

    
40
+ (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
41
	OpenStackRequest *request = [self requestWithoutToken:account method:method url:url];
42
	[request addRequestHeader:@"X-Auth-Token" value:account.authToken];
43
	return request;
44
}
45

    
46
+ (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
47
    NSString *urlString = [account.filesURL description];
48
    if (account.sharingAccount) {
49
        urlString = [NSString stringWithFormat:@"%@%@",
50
                     [urlString substringToIndex:[urlString rangeOfString:account.username].location],
51
                     account.sharingAccount];
52
    }
53
	NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@",
54
                                       urlString,
55
                                       path,
56
                                       (account.shared ? @"&shared=" : @"")]];
57
    return [self request:account method:method url:url];
58
}
59

    
60
#pragma mark NSCopying
61

    
62
- (id)copyWithZone:(NSZone *)zone {
63
    OpenStackRequest *newRequest = [super copyWithZone:zone];
64
    newRequest.account = self.account;
65
    newRequest.callback = self.callback;
66
    newRequest.errorAlerter = self.errorAlerter;
67
    newRequest.retryWithUpdatedURL = self.retryWithUpdatedURL;
68
    newRequest.followUpSelectorString = self.followUpSelectorString;
69
    newRequest.notificationURL = self.notificationURL;
70
    return newRequest;
71
}
72

    
73
#pragma mark Service Catalog
74
+ (id)serviceCatalogRequest:(OpenStackAccount *)account {
75
    OpenStackRequest *request = [self requestWithoutToken:account method:@"POST" url:account.provider.tokensURL];
76
    if (account.authToken.length)
77
        [request appendPostData:[[NSString stringWithFormat:@"{\"auth\":{\"token\":{\"id\":\"%@\"}}}", account.authToken]
78
                                 dataUsingEncoding:NSUTF8StringEncoding]];
79
    return request;
80
}
81

    
82
- (NSDictionary *)access {
83
    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
84
    NSDictionary *access = [[parser objectWithString:[self responseString]] objectForKey:@"access"];
85
    return access;
86
}
87

    
88
- (NSDictionary *)token {
89
    return [[self access] objectForKey:@"token"];
90
}
91

    
92
- (NSArray *)serviceCatalog {
93
    return [[self access] objectForKey:@"serviceCatalog"];
94
}
95

    
96
- (NSArray *)user {
97
    return [[self access] objectForKey:@"user"];
98
}
99

    
100
#pragma mark User Catalog
101
+ (id)userCatalogRequest:(OpenStackAccount *)account displaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs {
102
    OpenStackRequest *request = [self request:account method:@"POST" url:account.provider.userCatalogURL];
103
    NSMutableString *dataString = [NSMutableString stringWithString:@"{\"displaynames\":["];
104
    if (displaynames) {
105
        for (NSUInteger index = 0 ; index < displaynames.count ; index++) {
106
            [dataString appendFormat:@"\"%@\"%@",
107
             [[[displaynames objectAtIndex:index] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]
108
              stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""],
109
             ((index == displaynames.count - 1) ? @"" : @",")];
110
        }
111
    }
112
    [dataString appendFormat:@"],\"uuids\":["];
113
    if (UUIDs) {
114
        for (NSUInteger index = 0 ; index < UUIDs.count ; index++) {
115
            [dataString appendFormat:@"\"%@\"%@", [UUIDs objectAtIndex:index], ((index == UUIDs.count - 1) ? @"" : @",")];
116
        }
117
    }
118
    [dataString appendFormat:@"]}"];
119
    [request appendPostData:[dataString dataUsingEncoding:NSUTF8StringEncoding]];
120
    return request;
121
}
122

    
123
- (NSDictionary *)catalogs {
124
    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
125
    NSDictionary *catalogs = [parser objectWithString:[self responseString]];
126
    return catalogs;
127
}
128

    
129
- (NSDictionary *)displaynameCatalog {
130
    return [[self catalogs] objectForKey:@"displayname_catalog"];
131
}
132

    
133
- (NSDictionary *)UUIDCatalog {
134
    return [[self catalogs] objectForKey:@"uuid_catalog"];
135
}
136

    
137
#pragma mark Top
138

    
139
+ (id)authenticationRequest:(OpenStackAccount *)account {
140
	OpenStackRequest *request = [[[self alloc] initWithURL:account.provider.authEndpointURL] autorelease];
141
    request.account = account;
142
    request.requestMethod = @"GET";
143
    [request addRequestHeader:@"X-Auth-User" value:account.username];
144
    [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
145
    request.timeOutSeconds = 60;
146
    request.numberOfTimesToRetryOnTimeout = 5;
147
    request.validatesSecureCertificate = !account.ignoreSSLErrors;
148
    request.retryWithUpdatedURL = (account.provider.manual ? NO : YES);
149
	return request;
150
}
151

    
152
+ (id)getSharingAccountsRequest:(OpenStackAccount *)account
153
                         marker:(NSString *)marker sharingAccountsBuffer:(NSMutableDictionary *)sharingAccountsBuffer {
154
    OpenStackRequest *request = [self request:account method:@"GET"
155
                                          url:[NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json",
156
                                                                    account.provider.authEndpointURL]]];
157
    if (marker)
158
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&marker=%@", request.url.description, [NSString encodeToPercentEscape:marker]]];
159
    if (!sharingAccountsBuffer)
160
        sharingAccountsBuffer = [NSMutableDictionary dictionary];
161
    request.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
162
                        sharingAccountsBuffer, @"sharingAccountsBuffer",
163
                        nil];
164
    return request;
165
}
166

    
167
+ (id)getSharingAccountsRequest:(OpenStackAccount *)account {
168
    return [self getSharingAccountsRequest:account marker:nil sharingAccountsBuffer:nil];
169
}
170

    
171
- (NSArray *)jsonSharingAccounts {
172
    return [[[[SBJSON alloc] init] autorelease] objectWithString:[self responseString]];
173
}
174

    
175
#pragma mark Account
176

    
177
+ (id)getStorageAccountInfoRequest:(OpenStackAccount *)account {
178
    return [self filesRequest:account method:@"HEAD" path:@""];
179
}
180

    
181
+ (id)getContainersRequest:(OpenStackAccount *)account
182
                    marker:(NSString *)marker containersBuffer:(NSMutableDictionary *)containersBuffer {
183
    OpenStackRequest *request = [self filesRequest:account method:@"GET" path:@""];
184
    if (marker)
185
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&marker=%@", request.url.description, [NSString encodeToPercentEscape:marker]]];
186
    if (!containersBuffer)
187
        containersBuffer = [NSMutableDictionary dictionary];
188
    request.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
189
                        containersBuffer, @"containersBuffer",
190
                        nil];
191
    return request;
192
}
193

    
194
+ (id)getContainersRequest:(OpenStackAccount *)account {
195
    return [self getContainersRequest:account marker:nil containersBuffer:nil];
196
}
197

    
198
- (NSArray *)jsonContainers {
199
    return [[[[SBJSON alloc] init] autorelease] objectWithString:[self responseString]];
200
}
201

    
202
+ (id)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo {
203
    OpenStackRequest *request = [self filesRequest:account method:@"POST" path:@""];
204

    
205
    NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
206
    if (groups.count) {
207
        for (NSString *groupName in groups) {
208
            [request.requestHeaders setObject:[NSString encodeToPercentEscape:[groups objectForKey:groupName]]
209
                                       forKey:[NSString stringWithFormat:@"X-Account-Group-%@", [NSString encodeToPercentEscape:groupName]]];
210
        }
211
    } else {
212
        [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
213
    }
214
    
215
    NSMutableDictionary *metadata = [accountInfo objectForKey:@"metadata"];
216
    for (NSString *key in metadata) {
217
        [request.requestHeaders setObject:[NSString encodeToPercentEscape:[metadata objectForKey:key]]
218
                                   forKey:[NSString stringWithFormat:@"X-Account-Meta-%@", [NSString encodeToPercentEscape:key]]];
219
    }
220
    
221
    return request;
222
}
223

    
224
#pragma mark Container
225

    
226
+ (id)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container {
227
    return [self filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
228
}
229

    
230
+ (id)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {
231
    return [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
232
}
233

    
234
+ (id)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
235
    return [self filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
236
}
237

    
238
+ (id)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container
239
                 marker:(NSString *)marker objectsBuffer:(NSMutableDictionary *)objectsBuffer {
240
    OpenStackRequest *request = [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
241
    if (marker)
242
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&marker=%@", request.url.description, [NSString encodeToPercentEscape:marker]]];
243
    if (!objectsBuffer)
244
        objectsBuffer = [NSMutableDictionary dictionary];
245
    request.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
246
                        container, @"container",
247
                        objectsBuffer, @"objectsBuffer",
248
                        nil];
249
    return request;
250
}
251

    
252
+ (id)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
253
    return [self getObjectsRequest:account container:container marker:nil objectsBuffer:nil];
254
}
255

    
256
- (NSArray *)jsonObjects {
257
    return [[[[SBJSON alloc] init] autorelease] objectWithString:[self responseString]];
258
}
259

    
260
+ (id)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container {
261
    OpenStackRequest *request = [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
262
    [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
263
    [request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"];
264
    return request;
265
}
266

    
267
#pragma mark Storage Object
268

    
269
+ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
270
    NSString *objectFullPath = object.fullPath;
271
    if ([objectFullPath hasPrefix:@"/"])
272
        objectFullPath = [objectFullPath substringFromIndex:1];
273
    return [self filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
274
}
275

    
276
+ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version {
277
    OpenStackRequest *request = [self getObjectInfoRequest:account container:container object:object];
278
    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]];
279
    return request;
280
}
281

    
282
+ (id)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
283
    OpenStackRequest *request = [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
284
    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list", request.url.description]];
285
    return request;
286
}
287

    
288
- (NSMutableArray *)versions {
289
    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
290
    NSArray *jsonVersions = [[parser objectWithString:[self responseString]] objectForKey:@"versions"];
291
    NSMutableArray *versions = [NSMutableArray arrayWithCapacity:[jsonVersions count]];
292
    for (NSArray *jsonVersion in jsonVersions) {
293
        [versions addObject:[NSDictionary dictionaryWithObjectsAndKeys:
294
                             [jsonVersion objectAtIndex:0], @"versionID",
295
                             [NSDate dateWithTimeIntervalSince1970:[[jsonVersion objectAtIndex:1] doubleValue]], @"versionDate",
296
                             nil]];
297
    }
298
    return versions;
299
}
300

    
301

    
302
+ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
303
    return [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
304
}
305

    
306
+ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version {
307
    OpenStackRequest *request = [self getObjectRequest:account container:container object:object];
308
    if (version)
309
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]];
310
    return request;
311
}
312

    
313
+ (id)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
314
    NSString *objectFullPath = object.fullPath;
315
    if ([objectFullPath hasPrefix:@"/"])
316
        objectFullPath = [objectFullPath substringFromIndex:1];
317
    OpenStackRequest *request = [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
318

    
319
    if (object.sharing)
320
        [request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"];
321

    
322
    NSString *metadataKeyHeaderPrefix;
323
    if ([objectFullPath length] == 0)
324
        metadataKeyHeaderPrefix = @"X-Container-Meta-";
325
    else
326
        metadataKeyHeaderPrefix = @"X-Object-Meta-";
327
    for (NSString *metadataKey in object.metadata) {
328
        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey];
329
        metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
330
        NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
331
        [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
332
    }
333
    
334
	[request setPostBody:[NSMutableData dataWithData:object.data]];
335
    [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];
336
	return request;
337
}
338

    
339
+ (id)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
340
    NSString *objectFullPath = object.fullPath;
341
    if ([objectFullPath hasPrefix:@"/"])
342
        objectFullPath = [objectFullPath substringFromIndex:1];
343
    OpenStackRequest *request = [self filesRequest:account method:@"POST" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
344
    
345
    NSString *metadataKeyHeaderPrefix;
346
    if ([objectFullPath length] == 0)
347
        metadataKeyHeaderPrefix = @"X-Container-Meta-";
348
    else
349
        metadataKeyHeaderPrefix = @"X-Object-Meta-";
350
    for (NSString *metadataKey in object.metadata) {
351
        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey];
352
        metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
353
        NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
354
        [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
355
    }
356
    
357
    if (!account.sharingAccount) {
358
        [request.requestHeaders setObject:([object.publicURI length] ? @"true" : @"false") forKey:@"X-Object-Public"];
359
        if (object.sharing) {
360
            [request.requestHeaders setObject:[NSString encodeToPercentEscape:object.sharing] forKey:@"X-Object-Sharing"];
361
        }
362
    }
363
    return request;
364
}
365

    
366
+ (id)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
367
    NSString *objectFullPath = object.fullPath;
368
    if ([objectFullPath hasPrefix:@"/"])
369
        objectFullPath = [objectFullPath substringFromIndex:1];
370
    return [self filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
371
}
372

    
373
#pragma mark - ASIHTTPRequest Overrides
374

    
375
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
376
    [super setCompletionBlock:aCompletionBlock];
377
    [backupCompletionBlock release];
378
    backupCompletionBlock = [aCompletionBlock copy];
379
}
380

    
381
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
382
    [super setFailedBlock:aFailedBlock];
383
    [backupFailureBlock release];
384
    backupFailureBlock = [aFailedBlock copy];
385
}
386

    
387
- (void)failWithError:(NSError *)theError {
388
    if (responseStatusCode == 503) {
389
        NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
390
        [[NSNotificationCenter defaultCenter] postNotification:notification];
391
        // This crashes the app, check if it can be removed completely.
392
//    } else if (responseStatusCode == 0) {
393
//        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
394
//        if (![defaults boolForKey:@"already_failed_on_connection"]) {
395
//            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
396
//            [alert show];
397
//            [alert release];
398
//        }
399
//        [defaults setBool:YES forKey:@"already_failed_on_connection"];
400
//        [defaults synchronize];
401
    }
402
    
403
    [super failWithError:theError];
404
}
405

    
406
#pragma mark - Actions
407

    
408
- (BOOL)isSuccess {
409
	return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
410
}
411

    
412
#pragma mark - Notifications
413

    
414
- (void)notify:(NSString *)name {
415
    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
416
    NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo];
417
    [[NSNotificationCenter defaultCenter] postNotification:notification];
418
}
419

    
420
- (void)notify {
421
    NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, (self.notificationURL ? self.notificationURL.description : self.url.description)];
422
    NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, (self.notificationURL ? self.notificationURL.description : self.url.description), self.callback.uuid];
423

    
424
    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
425

    
426
    NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
427
    [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
428

    
429
    NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
430
    [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
431
    
432
}
433

    
434
#pragma mark - Memory Management
435

    
436
- (void)releaseBackupBlocksOnMainThread {
437
	NSMutableArray *blocks = [NSMutableArray array];
438
	if (backupCompletionBlock) {
439
		[blocks addObject:backupCompletionBlock];
440
		[backupCompletionBlock release];
441
		backupCompletionBlock = nil;
442
	}
443
	if (backupFailureBlock) {
444
		[blocks addObject:backupFailureBlock];
445
		[backupFailureBlock release];
446
		backupFailureBlock = nil;
447
	}
448
	[[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
449
}
450

    
451
// Always called on main thread
452
+ (void)releaseBackupBlocks:(NSArray *)blocks {
453
	// Blocks will be released when this method exits
454
}
455

    
456
- (void)dealloc {
457
    [account release];
458
    [errorAlerter release];
459
    [followUpSelectorString release];
460
    [notificationURL release];
461
    [self releaseBackupBlocksOnMainThread];
462
    [super dealloc];
463
}
464

    
465
@end