Statistics
| Branch: | Tag: | Revision:

root / Classes / OpenStackRequest.m @ 62ea6d49

History | View | Annotate | Download (21.8 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, retryBaseURL, retryType, 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 = NO;
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.retryWithUpdatedURL = (account.provider.manual ? NO : YES);
43
	[request addRequestHeader:@"X-Auth-Token" value:account.authToken];
44
	return request;
45
}
46

    
47
+ (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
48
    NSString *urlString = [[account.provider.authEndpointURL
49
                            URLByAppendingPathComponent:(account.sharingAccount ? account.sharingAccount : account.username)] description];
50
	NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@",
51
                                       urlString,
52
                                       path,
53
                                       (account.shared ? @"&shared=" : @"")]];
54
    OpenStackRequest *request = [self request:account method:method url:url];
55
    request.retryBaseURL = account.provider.authEndpointURL;
56
    request.retryType = OpenStackRequestTypeAuthEndpoint;
57
    return request;
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.retryBaseURL = self.retryBaseURL;
69
    newRequest.retryType = self.retryType;
70
    newRequest.followUpSelectorString = self.followUpSelectorString;
71
    newRequest.notificationURL = self.notificationURL;
72
    return newRequest;
73
}
74

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

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

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

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

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

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

    
127
- (NSDictionary *)catalogs {
128
    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
129
    NSDictionary *catalogs = [parser objectWithString:[self responseString]];
130
    return catalogs;
131
}
132

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

    
137
- (NSDictionary *)UUIDCatalog {
138
    return [[self catalogs] objectForKey:@"uuid_catalog"];
139
}
140

    
141
#pragma mark Top
142

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

    
158
+ (id)getSharingAccountsRequest:(OpenStackAccount *)account
159
                         marker:(NSString *)marker sharingAccountsBuffer:(NSMutableDictionary *)sharingAccountsBuffer {
160
    OpenStackRequest *request = [self request:account method:@"GET"
161
                                          url:[NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json",
162
                                                                    account.provider.authEndpointURL]]];
163
    request.retryBaseURL = account.provider.authEndpointURL;
164
    request.retryType = OpenStackRequestTypeAuthEndpoint;
165
    if (marker)
166
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&marker=%@", request.url.description, [NSString encodeToPercentEscape:marker]]];
167
    if (!sharingAccountsBuffer)
168
        sharingAccountsBuffer = [NSMutableDictionary dictionary];
169
    request.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
170
                        sharingAccountsBuffer, @"sharingAccountsBuffer",
171
                        nil];
172
    return request;
173
}
174

    
175
+ (id)getSharingAccountsRequest:(OpenStackAccount *)account {
176
    return [self getSharingAccountsRequest:account marker:nil sharingAccountsBuffer:nil];
177
}
178

    
179
- (NSArray *)jsonSharingAccounts {
180
    return [[[[SBJSON alloc] init] autorelease] objectWithString:[self responseString]];
181
}
182

    
183
#pragma mark Account
184

    
185
+ (id)getStorageAccountInfoRequest:(OpenStackAccount *)account {
186
    return [self filesRequest:account method:@"HEAD" path:@""];
187
}
188

    
189
+ (id)getContainersRequest:(OpenStackAccount *)account
190
                    marker:(NSString *)marker containersBuffer:(NSMutableDictionary *)containersBuffer {
191
    OpenStackRequest *request = [self filesRequest:account method:@"GET" path:@""];
192
    if (marker)
193
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&marker=%@", request.url.description, [NSString encodeToPercentEscape:marker]]];
194
    if (!containersBuffer)
195
        containersBuffer = [NSMutableDictionary dictionary];
196
    request.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
197
                        containersBuffer, @"containersBuffer",
198
                        nil];
199
    return request;
200
}
201

    
202
+ (id)getContainersRequest:(OpenStackAccount *)account {
203
    return [self getContainersRequest:account marker:nil containersBuffer:nil];
204
}
205

    
206
- (NSArray *)jsonContainers {
207
    return [[[[SBJSON alloc] init] autorelease] objectWithString:[self responseString]];
208
}
209

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

    
213
    NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
214
    if (groups.count) {
215
        for (NSString *groupName in groups) {
216
            [request.requestHeaders setObject:[NSString encodeToPercentEscape:[groups objectForKey:groupName]]
217
                                       forKey:[NSString stringWithFormat:@"X-Account-Group-%@", [NSString encodeToPercentEscape:groupName]]];
218
        }
219
    } else {
220
        [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
221
    }
222
    
223
    NSMutableDictionary *metadata = [accountInfo objectForKey:@"metadata"];
224
    for (NSString *key in metadata) {
225
        [request.requestHeaders setObject:[NSString encodeToPercentEscape:[metadata objectForKey:key]]
226
                                   forKey:[NSString stringWithFormat:@"X-Account-Meta-%@", [NSString encodeToPercentEscape:key]]];
227
    }
228
    
229
    return request;
230
}
231

    
232
#pragma mark Container
233

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

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

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

    
246
+ (id)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container
247
                 marker:(NSString *)marker objectsBuffer:(NSMutableDictionary *)objectsBuffer {
248
    OpenStackRequest *request = [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
249
    if (marker)
250
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&marker=%@", request.url.description, [NSString encodeToPercentEscape:marker]]];
251
    if (!objectsBuffer)
252
        objectsBuffer = [NSMutableDictionary dictionary];
253
    request.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
254
                        container, @"container",
255
                        objectsBuffer, @"objectsBuffer",
256
                        nil];
257
    return request;
258
}
259

    
260
+ (id)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
261
    return [self getObjectsRequest:account container:container marker:nil objectsBuffer:nil];
262
}
263

    
264
- (NSArray *)jsonObjects {
265
    return [[[[SBJSON alloc] init] autorelease] objectWithString:[self responseString]];
266
}
267

    
268
+ (id)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container {
269
    OpenStackRequest *request = [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
270
    [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
271
    [request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"];
272
    return request;
273
}
274

    
275
#pragma mark Storage Object
276

    
277
+ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
278
    NSString *objectFullPath = object.fullPath;
279
    if ([objectFullPath hasPrefix:@"/"])
280
        objectFullPath = [objectFullPath substringFromIndex:1];
281
    return [self filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
282
}
283

    
284
+ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version {
285
    OpenStackRequest *request = [self getObjectInfoRequest:account container:container object:object];
286
    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]];
287
    return request;
288
}
289

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

    
296
- (NSMutableArray *)versions {
297
    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
298
    NSArray *jsonVersions = [[parser objectWithString:[self responseString]] objectForKey:@"versions"];
299
    NSMutableArray *versions = [NSMutableArray arrayWithCapacity:[jsonVersions count]];
300
    for (NSArray *jsonVersion in jsonVersions) {
301
        [versions addObject:[NSDictionary dictionaryWithObjectsAndKeys:
302
                             [jsonVersion objectAtIndex:0], @"versionID",
303
                             [NSDate dateWithTimeIntervalSince1970:[[jsonVersion objectAtIndex:1] doubleValue]], @"versionDate",
304
                             nil]];
305
    }
306
    return versions;
307
}
308

    
309

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

    
314
+ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version {
315
    OpenStackRequest *request = [self getObjectRequest:account container:container object:object];
316
    if (version)
317
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]];
318
    return request;
319
}
320

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

    
327
    if (object.sharing)
328
        [request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"];
329

    
330
    NSString *metadataKeyHeaderPrefix;
331
    if ([objectFullPath length] == 0)
332
        metadataKeyHeaderPrefix = @"X-Container-Meta-";
333
    else
334
        metadataKeyHeaderPrefix = @"X-Object-Meta-";
335
    for (NSString *metadataKey in object.metadata) {
336
        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey];
337
        metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
338
        NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
339
        [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
340
    }
341
    
342
	[request setPostBody:[NSMutableData dataWithData:object.data]];
343
    [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];
344
	return request;
345
}
346

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

    
374
+ (id)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
375
    NSString *objectFullPath = object.fullPath;
376
    if ([objectFullPath hasPrefix:@"/"])
377
        objectFullPath = [objectFullPath substringFromIndex:1];
378
    return [self filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
379
}
380

    
381
#pragma mark - ASIHTTPRequest Overrides
382

    
383
- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
384
    [super setCompletionBlock:aCompletionBlock];
385
    [backupCompletionBlock release];
386
    backupCompletionBlock = [aCompletionBlock copy];
387
}
388

    
389
- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
390
    [super setFailedBlock:aFailedBlock];
391
    [backupFailureBlock release];
392
    backupFailureBlock = [aFailedBlock copy];
393
}
394

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

    
414
#pragma mark - Actions
415

    
416
- (BOOL)isSuccess {
417
	return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
418
}
419

    
420
#pragma mark - Notifications
421

    
422
- (void)notify:(NSString *)name {
423
    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
424
    NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo];
425
    [[NSNotificationCenter defaultCenter] postNotification:notification];
426
}
427

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

    
432
    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
433

    
434
    NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
435
    [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
436

    
437
    NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
438
    [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
439
    
440
}
441

    
442
#pragma mark - Memory Management
443

    
444
- (void)releaseBackupBlocksOnMainThread {
445
	NSMutableArray *blocks = [NSMutableArray array];
446
	if (backupCompletionBlock) {
447
		[blocks addObject:backupCompletionBlock];
448
		[backupCompletionBlock release];
449
		backupCompletionBlock = nil;
450
	}
451
	if (backupFailureBlock) {
452
		[blocks addObject:backupFailureBlock];
453
		[backupFailureBlock release];
454
		backupFailureBlock = nil;
455
	}
456
	[[self class] performSelectorOnMainThread:@selector(releaseBackupBlocks:) withObject:blocks waitUntilDone:[NSThread isMainThread]];
457
}
458

    
459
// Always called on main thread
460
+ (void)releaseBackupBlocks:(NSArray *)blocks {
461
	// Blocks will be released when this method exits
462
}
463

    
464
- (void)dealloc {
465
    [account release];
466
    [errorAlerter release];
467
    [retryBaseURL release];
468
    [followUpSelectorString release];
469
    [notificationURL release];
470
    [self releaseBackupBlocksOnMainThread];
471
    [super dealloc];
472
}
473

    
474
@end