Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ d8426ffb

History | View | Annotate | Download (107 kB)

1
//
2
//  PithosSyncDaemon.m
3
//  pithos-macos
4
//
5
// Copyright 2011-2012 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
15
//   2. Redistributions in binary form must reproduce the above
16
//      copyright notice, this list of conditions and the following
17
//      disclaimer in the documentation and/or other materials
18
//      provided with the distribution.
19
// 
20
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
// POSSIBILITY OF SUCH DAMAGE.
32
// 
33
// The views and conclusions contained in the software and
34
// documentation are those of the authors and should not be
35
// interpreted as representing official policies, either expressed
36
// or implied, of GRNET S.A.
37

    
38
#import "PithosSyncDaemon.h"
39
#import "PithosAccount.h"
40
#import "PithosLocalObjectState.h"
41
#import "PithosActivityFacility.h"
42
#import "PithosUtilities.h"
43
#import "ASINetworkQueue.h"
44
#import "ASIPithosRequest.h"
45
#import "ASIPithos.h"
46
#import "ASIPithosContainerRequest.h"
47
#import "ASIPithosObjectRequest.h"
48
#import "ASIPithosObject.h"
49

    
50
@interface PithosSyncDaemon (Private)
51
- (void)resetLocalState;
52
- (NSString *)pithosStateFilePath;
53
- (void)saveLocalState;
54

    
55
- (BOOL)moveToTempTrashFile:(NSString *)filePath;
56
- (void)emptyTempTrash;
57
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
58

    
59
- (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
60
- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
61
                                   object:(ASIPithosObject *)object 
62
                            localFilePath:(NSString *)filePath;
63
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest;
64
- (void)requestFailed:(ASIPithosRequest *)request;
65

    
66
- (void)syncOperationStarted;
67
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
68

    
69
@end
70

    
71
@implementation PithosSyncDaemon
72
@synthesize directoryPath, pithosAccount, containerName, pithos;
73
@synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
74
@synthesize containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
75

    
76
#pragma mark -
77
#pragma Object Lifecycle
78

    
79
- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
80
              pithosAccount:(PithosAccount *)aPithosAccount 
81
              containerName:(NSString *)aContainerName 
82
            resetLocalState:(BOOL)resetLocalState {
83
    if ((self = [super init])) {
84
        directoryPath = [aDirectoryPath copy];
85
        pithosAccount = [aPithosAccount retain];
86
        containerName = [aContainerName copy];
87
        self.pithos = pithosAccount.pithos;
88
        
89
        activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
90
        
91
        if (resetLocalState)
92
            [self resetLocalState];
93
        
94
        networkQueue = [[ASINetworkQueue alloc] init];
95
        networkQueue.showAccurateProgress = YES;
96
        networkQueue.shouldCancelAllRequestsOnFailure = NO;
97
//        networkQueue.maxConcurrentOperationCount = 1;
98
        
99
        callbackQueue = [[NSOperationQueue alloc] init];
100
        [callbackQueue setSuspended:YES];
101
        callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
102
//        callbackQueue.maxConcurrentOperationCount = 1;
103
        
104
        [[NSNotificationCenter defaultCenter] addObserver:self
105
                                                 selector:@selector(applicationWillTerminate:)
106
                                                     name:NSApplicationWillTerminateNotification
107
                                                   object:[NSApplication sharedApplication]];
108
        
109
        [self startDaemon];
110
    }
111
    return self;
112
}
113

    
114
- (void)resetLocalState {
115
    self.lastCompletedSync = nil;
116
    NSFileManager *fileManager = [NSFileManager defaultManager];
117
    NSError *error = nil;
118
    if ([fileManager fileExistsAtPath:self.pithosStateFilePath] && 
119
        (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
120
        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
121
                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath] 
122
                                                  error:error];
123
    if (self.tempDownloadsDirPath) {
124
        error = nil;
125
        for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
126
            if (error) {
127
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
128
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
129
                                                          error:error];
130
                break;
131
            }
132
            NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
133
            if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
134
                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
135
                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
136
                                                          error:error];
137
            }
138
            error = nil;
139
        }
140
    }
141
}
142

    
143
- (void)resetDaemon {
144
    @synchronized(self) {
145
        if (!daemonActive)
146
            return;
147
    }
148
    
149
    [networkQueue reset];
150
    [callbackQueue cancelAllOperations];
151
    [callbackQueue setSuspended:YES];
152
    [self emptyTempTrash];
153
    
154
    @synchronized(self) {            
155
        daemonActive = NO;
156
    }
157
}
158

    
159
- (void)startDaemon {
160
    @synchronized(self) {
161
        if (daemonActive)
162
            return;
163
    }
164
    
165
    syncOperationCount = 0;
166
    newSyncRequested = NO;
167
    syncIncomplete = NO;
168
    syncLate = NO;
169

    
170
    self.containerDirectoryPath = [directoryPath stringByAppendingPathComponent:containerName];
171
            
172
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
173
        NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
174
        NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
175
        self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
176
        [unarchiver finishDecoding];
177
    } else {
178
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
179
    }
180

    
181
    // In the improbable case of leftover operations
182
    [networkQueue reset];
183
    [callbackQueue cancelAllOperations];
184
    
185
    [networkQueue go];
186
    [callbackQueue setSuspended:NO];
187
    
188
    @synchronized(self) {
189
        daemonActive = YES;
190
    }
191
}
192

    
193
- (void)dealloc {
194
    [[NSNotificationCenter defaultCenter] removeObserver:self];
195
    [self resetDaemon];
196
    [callbackQueue release];
197
    [networkQueue release];
198
    [tempTrashDirPath release];
199
    [tempDownloadsDirPath release];
200
    [pithosStateFilePath release];
201
    [containerDirectoryPath release];
202
    [currentLocalObjectStates release];
203
    [storedLocalObjectStates release];
204
    [remoteObjects release];
205
    [objects release];
206
    [lastCompletedSync release];
207
    [lastModified release];
208
    [blockHash release];
209
    [pithos release];
210
    [containerName release];
211
    [pithosAccount release];
212
    [directoryPath release];
213
    [super dealloc];
214
}
215

    
216
#pragma mark -
217
#pragma mark Observers
218

    
219
- (void)applicationWillTerminate:(NSNotification *)notification {
220
    [self saveLocalState];
221
}
222

    
223
#pragma mark -
224
#pragma mark Properties
225

    
226
- (NSString *)pithosStateFilePath {
227
    if (!pithosStateFilePath)
228
        pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
229
                                 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
230
                                stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive", 
231
                                                                pithosAccount.uniqueName]] retain];
232
    return [[pithosStateFilePath copy] autorelease];
233
}
234

    
235
- (NSString *)tempDownloadsDirPath {
236
    if (!tempDownloadsDirPath) {
237
        tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
238
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
239
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads", 
240
                                                                 pithosAccount.uniqueName]] retain];
241
        NSFileManager *fileManager = [NSFileManager defaultManager];
242
        BOOL isDirectory;
243
        BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
244
        NSError *error = nil;
245
        if (fileExists && !isDirectory)
246
            [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
247
        if (!error & !fileExists)
248
            [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
249
        //if (error)
250
        //    tempDownloadsDirPath = nil;
251
        // XXX create a dir using mktmps?
252
    }
253
    return tempDownloadsDirPath;    
254
}
255

    
256
- (NSString *)tempTrashDirPath {
257
    if (!tempTrashDirPath) {
258
        tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
259
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
260
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash", 
261
                                                                 pithosAccount.uniqueName]] retain];
262
        NSFileManager *fileManager = [NSFileManager defaultManager];
263
        BOOL isDirectory;
264
        BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
265
        NSError *error = nil;
266
        if (fileExists && !isDirectory)
267
            [fileManager removeItemAtPath:tempTrashDirPath error:&error];
268
        if (!error & !fileExists)
269
            [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
270
        //if (error)
271
        //    tempTrashDirPath = nil;
272
        // XXX create a dir using mktmps?
273
    }
274
    return tempTrashDirPath;
275
}
276

    
277
- (void)setDirectoryPath:(NSString *)aDirectoryPath {
278
    if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
279
        [self resetDaemon];
280
        [self resetLocalState];
281
        [directoryPath release];
282
        directoryPath = [aDirectoryPath copy];
283
    }
284
}
285

    
286
- (void)setContainerName:(NSString *)aContainerName {
287
    if (aContainerName && ![aContainerName isEqualToString:containerName]) {
288
        [self resetDaemon];
289
        [self resetLocalState];
290
        [containerName release];
291
        containerName = [aContainerName copy];
292
    }    
293
}
294

    
295
- (void)setPithos:(ASIPithos *)aPithos {
296
    if (!pithos) {
297
        pithos = [[ASIPithos pithos] retain];
298
        pithos.authUser = [[aPithos.authUser copy] autorelease];
299
        pithos.authToken = [[aPithos.authToken copy] autorelease];
300
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
301
        pithos.authURL = [[aPithos.authURL copy] autorelease];
302
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
303
    }
304
    if (aPithos && 
305
        (![aPithos.authUser isEqualToString:pithos.authUser] || 
306
         ![aPithos.authToken isEqualToString:pithos.authToken] || 
307
         ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
308
        [self resetDaemon];
309
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
310
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
311
            [self resetLocalState];
312
        pithos.authUser = [[aPithos.authUser copy] autorelease];
313
        pithos.authToken = [[aPithos.authToken copy] autorelease];
314
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
315
        pithos.authURL = [[aPithos.authURL copy] autorelease];
316
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
317
    }        
318
}
319

    
320
#pragma mark -
321
#pragma mark Sync
322

    
323
- (void)saveLocalState {
324
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
325
    NSMutableData *data = [NSMutableData data];
326
    NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
327
    [archiver encodeObject:storedLocalObjectStates forKey:containerName];
328
    [archiver finishEncoding];
329
    [data writeToFile:self.pithosStateFilePath atomically:YES];
330
    [pool drain];
331
}
332

    
333
- (void)syncOperationStarted {
334
    @synchronized(self) {
335
        syncOperationCount++;
336
    }
337
}
338

    
339
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
340
    @synchronized(self) {
341
        if (!operationSuccessfull)
342
            syncIncomplete = YES;
343
        if (syncOperationCount == 0)
344
            // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
345
            return;
346
        syncOperationCount--;
347
        if (syncOperationCount == 0) {
348
            if (!syncIncomplete) {
349
                self.lastCompletedSync = [NSDate date];
350
                dispatch_async(dispatch_get_main_queue(), ^{
351
                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
352
                                                          message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync] 
353
                                                    pithosAccount:pithosAccount];
354
                    
355
                });
356
            }
357
            [self emptyTempTrash];
358
            if (newSyncRequested && daemonActive)
359
                [self sync];
360
        }
361
    }
362
}
363

    
364
- (BOOL)isSyncing {
365
    @synchronized(self) {
366
        return ((syncOperationCount > 0) && daemonActive);
367
    }
368
}
369

    
370
- (void)syncLate {
371
    @synchronized(self) {
372
        if ([self isSyncing])
373
            syncLate = YES;
374
    }
375
}
376

    
377
- (void)sync {
378
    @synchronized(self) {
379
        if ([self isSyncing]) {
380
            // If at least one operation is running return
381
            newSyncRequested = YES;
382
            return;
383
        } else if (daemonActive) {
384
            // The first operation is the server listing
385
            [self syncOperationStarted];
386
            newSyncRequested = NO;
387
            syncIncomplete = NO;
388
            syncLate = NO;
389
        } else {
390
            return;
391
        }
392
    }
393

    
394
    NSFileManager *fileManager = [NSFileManager defaultManager];
395
    BOOL isDirectory;
396
    NSError *error = nil;
397
    if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
398
        if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
399
            error) {
400
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
401
                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath] 
402
                                                      error:error];
403
            [self syncOperationFinishedWithSuccess:NO];
404
            return;
405
        }
406
    } else if (!isDirectory) {
407
        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
408
                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath] 
409
                                                  error:nil];
410
        [self syncOperationFinishedWithSuccess:NO];
411
        return;
412
    }
413
    
414
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
415
                                                                                            containerName:containerName 
416
                                                                                                    limit:0 
417
                                                                                                   marker:nil 
418
                                                                                                   prefix:nil 
419
                                                                                                delimiter:nil 
420
                                                                                                     path:nil 
421
                                                                                                     meta:nil 
422
                                                                                                   shared:NO 
423
                                                                                                    until:nil 
424
                                                                                          ifModifiedSince:lastModified];
425
    containerRequest.delegate = self;
426
    containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
427
    containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
428
    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
429
                                                               message:@"Sync: Getting server listing" 
430
                                                         pithosAccount:pithosAccount];
431
    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
432
                                 activity, @"activity", 
433
                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
434
                                 @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
435
                                 @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
436
                                 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
437
                                 [NSNumber numberWithUnsignedInteger:10], @"retries", 
438
                                 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector", 
439
                                 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector", 
440
                                 nil];
441
    [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
442
}
443

    
444
- (void)emptyTempTrash {
445
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
446
    NSString *trashDirPath = self.tempTrashDirPath;
447
    if (trashDirPath) {
448
        NSFileManager *fileManager = [NSFileManager defaultManager];
449
        NSError *error = nil;
450
//        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
451
        NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
452
        if (error) {
453
            dispatch_async(dispatch_get_main_queue(), ^{
454
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
455
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
456
                                                          error:error];
457
            });
458
            [pool drain];
459
            return;
460
        }
461
        if ([subPaths count]) {
462
//            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
463
//            for (NSString *subPath in subPaths) {
464
//                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
465
//            }
466
//            [self syncOperationStarted];
467
//            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
468
//                if (error) {
469
//                    dispatch_async(dispatch_get_main_queue(), ^{
470
//                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
471
//                                                                message:@"Cannot move files to Trash" 
472
//                                                                  error:error];
473
//                    });
474
//                }
475
//                [self syncOperationFinishedWithSuccess:YES];
476
//            }];
477
            for (NSString *subPath in subPaths) {
478
                NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
479
                error = nil;
480
                if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
481
                    dispatch_async(dispatch_get_main_queue(), ^{
482
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
483
                                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
484
                                                                  error:error];
485
                    });
486
            }
487
        }
488
    }
489
    [pool drain];
490
}
491

    
492
- (BOOL)moveToTempTrashFile:(NSString *)filePath {
493
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
494
    NSString *trashDirPath = self.tempTrashDirPath;
495
    if (!tempTrashDirPath) {
496
        [pool drain];
497
        return NO;
498
    }
499
    NSFileManager *fileManager = [NSFileManager defaultManager];
500
    BOOL isDirectory;
501
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
502
    NSError *error = nil;
503
    NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
504
                                                                withString:trashDirPath];
505
    NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
506
    if (fileExists && isDirectory) {
507
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
508
        if (error) {
509
            dispatch_async(dispatch_get_main_queue(), ^{
510
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
511
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
512
                                                          error:error];
513
            });
514
            [pool drain];
515
            return NO;
516
        }
517
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
518
            dispatch_async(dispatch_get_main_queue(), ^{
519
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
520
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
521
                                                          error:error];
522
            });
523
            [pool drain];
524
            return NO;
525
        }
526
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
527
            dispatch_async(dispatch_get_main_queue(), ^{
528
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
529
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
530
                                                                 filePath, newFilePath] 
531
                                                          error:error];
532
            });
533
            [pool drain];
534
            return NO;
535
        }
536
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
537
        if (currentState) {
538
            currentState.filePath = newFilePath;
539
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
540
            [currentLocalObjectStates removeObjectForKey:filePath];        
541
        } else {
542
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
543
                                                                                       blockHash:blockHash 
544
                                                                                       blockSize:blockSize] 
545
                                         forKey:newFilePath];
546
        }
547
        for (NSString *subPath in subPaths) {
548
            NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
549
            NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
550
                                                                              withString:trashDirPath];
551
            currentState = [currentLocalObjectStates objectForKey:subFilePath];
552
            if (currentState) {
553
                currentState.filePath = newSubFilePath;
554
                [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
555
                [currentLocalObjectStates removeObjectForKey:subFilePath];
556
            } else {
557
                [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
558
                                                                                           blockHash:blockHash 
559
                                                                                           blockSize:blockSize] 
560
                                             forKey:newSubFilePath];
561
            }        
562
        }
563
    } else if (fileExists && !isDirectory) {
564
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
565
            dispatch_async(dispatch_get_main_queue(), ^{
566
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
567
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
568
                                                          error:error];
569
            });
570
            [pool drain];
571
            return NO;
572
        }
573
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
574
            dispatch_async(dispatch_get_main_queue(), ^{
575
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
576
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
577
                                                                 filePath, newFilePath] 
578
                                                          error:error];
579
            });
580
            [pool drain];
581
            return NO;
582
        }
583
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
584
        if (currentState) {
585
            currentState.filePath = newFilePath;
586
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
587
            [currentLocalObjectStates removeObjectForKey:filePath];        
588
        } else {
589
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
590
                                                                                       blockHash:blockHash 
591
                                                                                       blockSize:blockSize] 
592
                                         forKey:newFilePath];
593
        }
594
    }
595
    [pool drain];
596
    return YES;
597
}
598

    
599
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
600
    if ([hash length] != 64)
601
        return NO;
602
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
603
    PithosLocalObjectState *localState;
604
    NSFileManager *fileManager = [NSFileManager defaultManager];
605
    BOOL isDirectory;
606
    NSError *error = nil;
607
    for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
608
        localState = [currentLocalObjectStates objectForKey:localFilePath];
609
        if (!localState.isDirectory && [hash isEqualToString:localState.hash] && 
610
            [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
611
            if ([localFilePath hasPrefix:containerDirectoryPath]) {
612
                if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
613
                    dispatch_async(dispatch_get_main_queue(), ^{
614
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
615
                                                                message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
616
                                                                         localFilePath, filePath] 
617
                                                                  error:error];
618
                    });
619
                } else {
620
                    [pool drain];
621
                    return YES;
622
                }            
623
            } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
624
                if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
625
                    dispatch_async(dispatch_get_main_queue(), ^{
626
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
627
                                                                message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
628
                                                                         localFilePath, filePath] 
629
                                                                  error:error];
630
                    });
631
                } else {
632
                    localState.filePath = filePath;
633
                    [currentLocalObjectStates setObject:localState forKey:filePath];
634
                    [currentLocalObjectStates removeObjectForKey:localFilePath];
635
                    [pool drain];
636
                    return YES;
637
                }
638
            }
639
        }
640
    }
641
    [pool drain];
642
    return NO;
643
}
644

    
645
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
646
                     localFilePath:(NSString *)filePath {
647
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
648
    NSFileManager *fileManager = [NSFileManager defaultManager];
649
    NSError *error;
650
    BOOL isDirectory;
651
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
652
    NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
653
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
654
    // Remote updated info
655
    NSError *remoteError;
656
    BOOL remoteIsDirectory;
657
    BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos 
658
                                                      containerName:containerName 
659
                                                         objectName:object.name 
660
                                                              error:&remoteError 
661
                                                        isDirectory:&remoteIsDirectory 
662
                                                     sharingAccount:nil];
663
    if (!object || !object.objectHash) {
664
        // Delete local object
665
        if (remoteObjectExists) {
666
            // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
667
            syncIncomplete = YES;
668
        }
669
        NSLog(@"Sync::delete local object: %@", filePath);
670
        if (!fileExists || [self moveToTempTrashFile:filePath]) {
671
            dispatch_async(dispatch_get_main_queue(), ^{
672
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
673
                                                      message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name] 
674
                                                pithosAccount:pithosAccount];
675
            });
676
            [storedLocalObjectStates removeObjectForKey:object.name];
677
            [self saveLocalState];
678
        }
679
    } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
680
        // Create local directory object
681
        if (!remoteObjectExists || !remoteIsDirectory) {
682
            // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
683
            syncIncomplete = YES;
684
            [pool drain];
685
            return;
686
        }
687
        NSLog(@"Sync::create local directory object: %@", filePath);
688
        BOOL directoryCreated = NO;
689
        if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
690
            NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
691
            error = nil;
692
            if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
693
                dispatch_async(dispatch_get_main_queue(), ^{
694
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
695
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
696
                                                              error:error];
697
                });
698
            } else {
699
                directoryCreated = YES;
700
                storedState.filePath = filePath;
701
                storedState.isDirectory = YES;
702
                [self saveLocalState];
703
            }
704
        } else {
705
            NSLog(@"Sync::local directory object exists: %@", filePath);
706
            directoryCreated = YES;
707
        }
708
        if (directoryCreated)
709
            dispatch_async(dispatch_get_main_queue(), ^{
710
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
711
                                                      message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name] 
712
                                                pithosAccount:pithosAccount];
713
            });
714
    } else if (object.bytes == 0) {
715
        // Create local object with zero length
716
        if (!remoteObjectExists || remoteIsDirectory) {
717
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
718
            syncIncomplete = YES;
719
            [pool drain];
720
            return;
721
        }
722
        NSLog(@"Sync::create local zero length object: %@", filePath);
723
        BOOL fileCreated = NO;
724
        if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
725
            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
726
            // Create directory of the file, if it doesn't exist
727
            error = nil;
728
            if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
729
                dispatch_async(dispatch_get_main_queue(), ^{
730
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
731
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
732
                                                              error:error];
733
                });
734
            }
735
            error = nil;
736
            if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
737
                dispatch_async(dispatch_get_main_queue(), ^{
738
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
739
                                                            message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
740
                                                              error:error];
741
                });
742
            } else {
743
                fileCreated = YES;
744
                storedState.filePath = filePath;
745
                storedState.hash = object.objectHash;
746
                storedState.tmpFilePath = nil;
747
                [self saveLocalState];
748
            }
749
        } else {
750
            NSLog(@"Sync::local zero length object exists: %@", filePath);
751
            fileCreated = YES;
752
        }
753
        if (fileCreated)
754
            dispatch_async(dispatch_get_main_queue(), ^{
755
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
756
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name] 
757
                                                pithosAccount:pithosAccount];
758
            });
759
    } else if (storedState.tmpFilePath == nil) {
760
        // Create new local object
761
        if (!remoteObjectExists || remoteIsDirectory) {
762
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
763
            syncIncomplete = YES;
764
            [pool drain];
765
            return;
766
        }
767
        // Create directory of the file, if it doesn't exist
768
        error = nil;
769
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
770
            dispatch_async(dispatch_get_main_queue(), ^{
771
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
772
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
773
                                                          error:error];
774
            });
775
        }
776
        // Check first if a local copy exists
777
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
778
            storedState.filePath = filePath;
779
            storedState.hash = object.objectHash;
780
            [self saveLocalState];
781
            dispatch_async(dispatch_get_main_queue(), ^{
782
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
783
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name] 
784
                                                pithosAccount:pithosAccount];
785
            });
786
        } else {
787
            [self syncOperationStarted];
788
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
789
                                                                                                containerName:containerName 
790
                                                                                                       object:object 
791
                                                                                                   blockIndex:0 
792
                                                                                                    blockSize:blockSize];
793
            objectRequest.delegate = self;
794
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
795
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
796
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
797
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
798
                                                                    totalBytes:object.bytes 
799
                                                                  currentBytes:0 
800
                                                                 pithosAccount:pithosAccount];
801
            dispatch_async(dispatch_get_main_queue(), ^{
802
                [activityFacility updateActivity:activity withMessage:activity.message];
803
            });
804
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
805
                                      object, @"pithosObject", 
806
                                      [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks", 
807
                                      [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
808
                                      filePath, @"filePath", 
809
                                      activity, @"activity", 
810
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
811
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
812
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
813
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
814
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
815
                                      NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector", 
816
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
817
                                      nil];
818
            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
819
                [activityFacility updateActivity:activity 
820
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
821
                                                  [[objectRequest.userInfo objectForKey:@"pithosObject"] name], 
822
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
823
                                      totalBytes:activity.totalBytes 
824
                                    currentBytes:(activity.currentBytes + size)];
825
            }];
826
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
827
        }
828
    } else {
829
        // Resume local object download
830
        if (!remoteObjectExists || remoteIsDirectory) {
831
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
832
            syncIncomplete = YES;
833
            [pool drain];
834
            return;
835
        }
836
        // Create directory of the file, if it doesn't exist
837
        error = nil;
838
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
839
            dispatch_async(dispatch_get_main_queue(), ^{
840
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
841
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
842
                                                          error:error];
843
            });
844
        }
845
        // Check first if a local copy exists
846
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
847
            storedState.filePath = filePath;
848
            storedState.hash = object.objectHash;
849
            // Delete incomplete temp download
850
            storedState.tmpFilePath = nil;
851
            [self saveLocalState];
852
            dispatch_async(dispatch_get_main_queue(), ^{
853
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
854
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name] 
855
                                                pithosAccount:pithosAccount];
856
            });
857
        } else {
858
            [self syncOperationStarted];
859
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos 
860
                                                                                             containerName:containerName 
861
                                                                                                objectName:object.name];
862
            objectRequest.delegate = self;
863
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
864
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
865
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
866
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
867
                                                                    totalBytes:object.bytes 
868
                                                                  currentBytes:0 
869
                                                                 pithosAccount:pithosAccount];
870
            dispatch_async(dispatch_get_main_queue(), ^{
871
                [activityFacility updateActivity:activity withMessage:activity.message];
872
            });
873
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
874
                                      object, @"pithosObject", 
875
                                      filePath, @"filePath", 
876
                                      activity, @"activity", 
877
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
878
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
879
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
880
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
881
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
882
                                      NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector", 
883
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
884
                                      nil];
885
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
886
        }
887
    }
888
    [pool drain];
889
}
890

    
891
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
892
                                  object:(ASIPithosObject *)object 
893
                           localFilePath:(NSString *)filePath {
894
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
895
    [self syncOperationStarted];
896
    NSFileManager *fileManager = [NSFileManager defaultManager];
897
    BOOL isDirectory;
898
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
899
    if (currentState.isDirectory) {
900
        // Create remote directory object
901
        if (!fileExists || !isDirectory) {
902
            // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
903
            [self syncOperationFinishedWithSuccess:NO];
904
            [pool drain];
905
            return;
906
        }
907
        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
908
                                                                                           containerName:containerName 
909
                                                                                              objectName:object.name 
910
                                                                                                    eTag:nil 
911
                                                                                             contentType:@"application/directory" 
912
                                                                                         contentEncoding:nil 
913
                                                                                      contentDisposition:nil 
914
                                                                                                manifest:nil 
915
                                                                                                 sharing:nil 
916
                                                                                                isPublic:ASIPithosObjectRequestPublicIgnore 
917
                                                                                                metadata:nil 
918
                                                                                                    data:[NSData data]];
919
        objectRequest.delegate = self;
920
        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
921
        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
922
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
923
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name] 
924
                                                             pithosAccount:pithosAccount];
925
        dispatch_async(dispatch_get_main_queue(), ^{
926
            [activityFacility updateActivity:activity withMessage:activity.message];
927
        });
928
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
929
                                  object, @"pithosObject", 
930
                                  activity, @"activity", 
931
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage", 
932
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage", 
933
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage", 
934
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
935
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
936
                                  NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
937
                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
938
                                  nil];
939
        [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
940
    } else if (![currentState exists]) {
941
        // Delete remote object
942
        if (fileExists) {
943
            // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
944
            syncIncomplete = YES;
945
        }
946
        NSString *safeName;
947
        if ([PithosUtilities isContentTypeDirectory:object.contentType])
948
            safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
949
        else
950
            safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
951
        if (safeName) {
952
            ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
953
                                                                                   containerName:containerName 
954
                                                                                      objectName:object.name 
955
                                                                        destinationContainerName:@"trash" 
956
                                                                           destinationObjectName:safeName 
957
                                                                                   checkIfExists:NO];
958
            if (objectRequest) {
959
                objectRequest.delegate = self;
960
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
961
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
962
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
963
                                                                           message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name] 
964
                                                                     pithosAccount:pithosAccount];
965
                dispatch_async(dispatch_get_main_queue(), ^{
966
                    [activityFacility updateActivity:activity withMessage:activity.message];
967
                });
968
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
969
                                          object, @"pithosObject", 
970
                                          activity, @"activity", 
971
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage", 
972
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage", 
973
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage", 
974
                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
975
                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
976
                                          NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector", 
977
                                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
978
                                          nil];
979
                [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
980
            } else {
981
                [self syncOperationFinishedWithSuccess:NO];
982
            }
983
        } else {
984
            [self syncOperationFinishedWithSuccess:NO];
985
        }
986
    } else {
987
        // Upload file to remote object
988
        if (!fileExists || isDirectory) {
989
            // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
990
            [self syncOperationFinishedWithSuccess:NO];
991
            [pool drain];
992
            return;
993
        }
994
        NSError *error = nil;
995
        object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
996
        if (object.contentType == nil)
997
            object.contentType = @"application/octet-stream";
998
        if (error)
999
            NSLog(@"contentType detection error: %@", error);
1000
        NSArray *hashes = nil;
1001
        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1002
                                                                                    containerName:containerName 
1003
                                                                                       objectName:object.name 
1004
                                                                                      contentType:object.contentType 
1005
                                                                                        blockSize:blockSize 
1006
                                                                                        blockHash:blockHash 
1007
                                                                                          forFile:filePath 
1008
                                                                                    checkIfExists:NO 
1009
                                                                                           hashes:&hashes 
1010
                                                                                   sharingAccount:nil];
1011
        if (objectRequest) {
1012
            objectRequest.delegate = self;
1013
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1014
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1015
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1016
                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
1017
                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1018
                                                                  currentBytes:0 
1019
                                                                 pithosAccount:pithosAccount];
1020
            dispatch_async(dispatch_get_main_queue(), ^{
1021
                [activityFacility updateActivity:activity withMessage:activity.message];
1022
            });
1023
            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1024
             [NSDictionary dictionaryWithObjectsAndKeys:
1025
              object, @"pithosObject", 
1026
              filePath, @"filePath", 
1027
              hashes, @"hashes", 
1028
              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1029
              activity, @"activity", 
1030
              [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
1031
              [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
1032
              [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
1033
              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1034
              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1035
              NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1036
              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1037
              nil]];
1038
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1039
        } else {
1040
            [self syncOperationFinishedWithSuccess:NO];
1041
        }
1042
    }
1043
    [pool drain];
1044
}
1045

    
1046
#pragma mark -
1047
#pragma mark ASIHTTPRequestDelegate
1048

    
1049
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1050
    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1051
    NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1052
                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1053
                                                                               object:request] autorelease];
1054
    operation.completionBlock = ^{
1055
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1056
        if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1057
            dispatch_async(dispatch_get_main_queue(), ^{
1058
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1059
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1060
            });
1061
            [self syncOperationFinishedWithSuccess:NO];
1062
        }
1063
        [pool drain];
1064
    };
1065
    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1066
    [callbackQueue addOperation:operation];
1067
}
1068

    
1069
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1070
    if (request.isCancelled) {
1071
        // Request has been cancelled 
1072
        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1073
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1074
                   withObject:request];
1075
    } else {
1076
        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1077
        NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1078
                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1079
                                                                                   object:request] autorelease];
1080
        operation.completionBlock = ^{
1081
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1082
            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1083
                dispatch_async(dispatch_get_main_queue(), ^{
1084
                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1085
                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1086
                });
1087
                [self syncOperationFinishedWithSuccess:NO];
1088
            }
1089
            [pool drain];
1090
        };
1091
        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1092
        [callbackQueue addOperation:operation];
1093
    }
1094
}
1095

    
1096
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1097
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1098
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1099
    NSLog(@"Sync::list request finished: %@", containerRequest.url);
1100
    if (operation.isCancelled) {
1101
        [self listRequestFailed:containerRequest];
1102
    } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
1103
        if (containerRequest.responseStatusCode == 200) {
1104
            NSArray *someObjects = [containerRequest objects];
1105
            if (objects == nil) {
1106
                objects = [[NSMutableArray alloc] initWithArray:someObjects];
1107
            } else {
1108
                [objects addObjectsFromArray:someObjects];
1109
            }
1110
            if ([someObjects count] < 10000) {
1111
                self.blockHash = [containerRequest blockHash];
1112
                self.blockSize = [containerRequest blockSize];
1113
                self.lastModified = [containerRequest lastModified];
1114
                self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1115
                for (ASIPithosObject *object in objects) {
1116
                    [remoteObjects setObject:object forKey:object.name];
1117
                }
1118
                [objects release];
1119
                objects = nil;
1120
            } else {
1121
                // Do an additional request to fetch more objects
1122
                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1123
                                                                                                           containerName:containerName 
1124
                                                                                                                   limit:0 
1125
                                                                                                                  marker:[[someObjects lastObject] name] 
1126
                                                                                                                  prefix:nil 
1127
                                                                                                               delimiter:nil 
1128
                                                                                                                    path:nil 
1129
                                                                                                                    meta:nil 
1130
                                                                                                                  shared:NO 
1131
                                                                                                                   until:nil 
1132
                                                                                                         ifModifiedSince:lastModified];
1133
                newContainerRequest.delegate = self;
1134
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1135
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1136
                newContainerRequest.userInfo = containerRequest.userInfo;
1137
                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1138
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1139
                [pool drain];
1140
                return;
1141
            }
1142
        }
1143
        
1144
        if (operation.isCancelled) {
1145
            [self listRequestFailed:containerRequest];
1146
            [pool drain];
1147
            return;
1148
        }
1149

    
1150
        dispatch_async(dispatch_get_main_queue(), ^{
1151
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1152
                              withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1153
        });
1154
        NSFileManager *fileManager = [NSFileManager defaultManager];
1155
        NSError *error = nil;
1156
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
1157
        if (error) {
1158
            dispatch_async(dispatch_get_main_queue(), ^{
1159
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
1160
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
1161
                                                          error:error];
1162
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
1163
                                                      message:@"Sync: Failed to read contents of sync directory" 
1164
                                                pithosAccount:pithosAccount];
1165
            });
1166
            // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1167
            [self syncOperationFinishedWithSuccess:NO];
1168
            [pool drain];
1169
            return;
1170
        }
1171
        
1172
        if (operation.isCancelled) {
1173
            operation.completionBlock = nil;
1174
            [self syncOperationFinishedWithSuccess:NO];
1175
            [pool drain];
1176
            return;
1177
        }
1178

    
1179
        self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
1180
        for (NSString *objectName in subPaths) {
1181
            if (operation.isCancelled) {
1182
                operation.completionBlock = nil;
1183
                [self saveLocalState];
1184
                [self syncOperationFinishedWithSuccess:NO];
1185
                [pool drain];
1186
                return;
1187
            }
1188

    
1189
            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:objectName];
1190
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1191
            if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1192
                // New or modified existing local object, compute current state
1193
                if (!storedLocalObjectState)
1194
                    // For new local object, also create empty stored state
1195
                    [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1196
                [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
1197
                                                                                           blockHash:blockHash 
1198
                                                                                           blockSize:blockSize] 
1199
                                             forKey:filePath];
1200
            } else {
1201
                // Local object hasn't changed, set stored state also to current
1202
                [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1203
            }
1204
        }
1205
        [self saveLocalState];
1206

    
1207
        if (operation.isCancelled) {
1208
            operation.completionBlock = nil;
1209
            [self syncOperationFinishedWithSuccess:NO];
1210
            [pool drain];
1211
            return;
1212
        }
1213

    
1214
        for (NSString *objectName in remoteObjects) {
1215
            if (operation.isCancelled) {
1216
                operation.completionBlock = nil;
1217
                [self saveLocalState];
1218
                [self syncOperationFinishedWithSuccess:NO];
1219
                [pool drain];
1220
                return;
1221
            }
1222

    
1223
            if (![storedLocalObjectStates objectForKey:objectName])
1224
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1225
        }
1226
        [self saveLocalState];
1227

    
1228
        if (operation.isCancelled) {
1229
            operation.completionBlock = nil;
1230
            [self syncOperationFinishedWithSuccess:NO];
1231
            [pool drain];
1232
            return;
1233
        }
1234

    
1235
        for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1236
            if (operation.isCancelled) {
1237
                operation.completionBlock = nil;
1238
                [self syncOperationFinishedWithSuccess:NO];
1239
                [pool drain];
1240
                return;
1241
            }
1242

    
1243
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1244
            if ([objectName hasSuffix:@"/"])
1245
                filePath = [filePath stringByAppendingString:@":"];
1246
            ASIPithosObject *object = [ASIPithosObject object];
1247
            object.name = objectName;
1248
            NSLog(@"Sync::object name: %@", objectName);
1249
            
1250
            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
1251
            PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1252
            if (!currentLocalObjectState)
1253
                currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
1254
                                                                                 blockHash:blockHash 
1255
                                                                                 blockSize:blockSize];
1256
            if (currentLocalObjectState.isDirectory)
1257
                object.contentType = @"application/directory";
1258
            
1259
            PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1260
            ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
1261
            if (remoteObject) {
1262
                if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1263
                    remoteObjectState.isDirectory = YES;
1264
                    object.contentType = @"application/directory";
1265
                } else {
1266
                    remoteObjectState.hash = remoteObject.objectHash;
1267
                }
1268
            }
1269

    
1270
            BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1271
            BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1272
            NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1273
            if (!localStateHasChanged) {
1274
                // Local state hasn't changed
1275
                if (serverStateHasChanged) {
1276
                    // Server state has changed
1277
                    // Update local state to match that of the server 
1278
                    object.bytes = remoteObject.bytes;
1279
                    object.version = remoteObject.version;
1280
                    object.contentType = remoteObject.contentType;
1281
                    object.objectHash = remoteObject.objectHash;
1282
                    [self updateLocalStateWithObject:object localFilePath:filePath];
1283
                } else if (!remoteObject && ![currentLocalObjectState exists]) {
1284
                    // Server state hasn't changed
1285
                    // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1286
                    [storedLocalObjectStates removeObjectForKey:objectName];
1287
                    [self saveLocalState];
1288
                }
1289
            } else {
1290
                // Local state has changed
1291
                if (!serverStateHasChanged) {
1292
                    // Server state hasn't changed
1293
                    [self updateServerStateWithCurrentState:currentLocalObjectState 
1294
                                                     object:object 
1295
                                              localFilePath:filePath];
1296
                } else {
1297
                    // Server state has also changed
1298
                    if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1299
                        // Both did the same change (directory)
1300
                        storedLocalObjectState.filePath = filePath;
1301
                        storedLocalObjectState.isDirectory = YES;
1302
                        [self saveLocalState];
1303
                    } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1304
                        // Both did the same change (object edit or delete)
1305
                        if (![remoteObjectState exists]) {
1306
                            [storedLocalObjectStates removeObjectForKey:object.name];
1307
                        } else {
1308
                            storedLocalObjectState.filePath = filePath;
1309
                            storedLocalObjectState.hash = remoteObjectState.hash;
1310
                        }
1311
                        [self saveLocalState];
1312
                    } else {
1313
                        // Conflict, we ask the user which change to keep
1314
                        NSString *informativeText;
1315
                        NSString *firstButtonText;
1316
                        NSString *secondButtonText;
1317
                        
1318
                        if (![remoteObjectState exists]) {
1319
                            // Remote object has been deleted
1320
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
1321
                            firstButtonText = @"Delete local file";
1322
                            secondButtonText = @"Upload file to server";
1323
                        } else if (![currentLocalObjectState exists]) {
1324
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
1325
                            firstButtonText = @"Download file from server";
1326
                            secondButtonText = @"Delete file on server";
1327
                        } else {
1328
                            informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
1329
                            firstButtonText = @"Keep server version";
1330
                            secondButtonText = @"Keep local version";
1331
                        }
1332
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1333
                        [alert setMessageText:@"Conflict"];
1334
                        [alert setInformativeText:informativeText];
1335
                        [alert addButtonWithTitle:firstButtonText];
1336
                        [alert addButtonWithTitle:secondButtonText];
1337
                        [alert addButtonWithTitle:@"Do nothing"];
1338
                        NSInteger choice = [alert runModal];
1339
                        if (choice == NSAlertFirstButtonReturn) {
1340
                            object.bytes = remoteObject.bytes;
1341
                            object.version = remoteObject.version;
1342
                            object.contentType = remoteObject.contentType;
1343
                            object.objectHash = remoteObject.objectHash;
1344
                            [self updateLocalStateWithObject:object localFilePath:filePath];
1345
                        } if (choice == NSAlertSecondButtonReturn) {
1346
                            [self updateServerStateWithCurrentState:currentLocalObjectState 
1347
                                                             object:object 
1348
                                                      localFilePath:filePath];
1349
                        }
1350
                    }
1351
                }
1352
            }
1353
        }
1354
        [self syncOperationFinishedWithSuccess:YES];
1355
    } else {
1356
        [self listRequestFailed:containerRequest];
1357
    }
1358
    [pool drain];
1359
}
1360

    
1361
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1362
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1363
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1364
    if (operation.isCancelled) {
1365
        [objects release];
1366
        objects = nil;
1367
        [pool drain];
1368
        return;        
1369
    }
1370
    if (containerRequest.isCancelled) {
1371
        dispatch_async(dispatch_get_main_queue(), ^{
1372
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1373
                              withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1374
        });
1375
        [objects release];
1376
        objects = nil;
1377
        [self syncOperationFinishedWithSuccess:NO];
1378
        [pool drain];
1379
        return;
1380
    }
1381
    // If the server listing fails, the sync should start over, so just retrying is enough
1382
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1383
    if (retries > 0) {
1384
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1385
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1386
        [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1387
    } else {
1388
        dispatch_async(dispatch_get_main_queue(), ^{
1389
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1390
                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1391
        });
1392
        [objects release];
1393
        objects = nil;
1394
        // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1395
        [self syncOperationFinishedWithSuccess:NO];
1396
    }
1397
    [pool drain];
1398
}
1399

    
1400
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1401
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1402
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1403
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1404
    if (operation.isCancelled) {
1405
        [self requestFailed:objectRequest];
1406
    } else if (objectRequest.responseStatusCode == 206) {
1407
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1408
        NSFileManager *fileManager = [NSFileManager defaultManager];
1409
        NSError *error;
1410
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1411
        
1412
        NSString *downloadsDirPath = self.tempDownloadsDirPath;
1413
        if (!downloadsDirPath) {
1414
            dispatch_async(dispatch_get_main_queue(), ^{
1415
                [activityFacility endActivity:activity 
1416
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1417
            });
1418
            [self syncOperationFinishedWithSuccess:NO];
1419
            [pool drain];
1420
            return;
1421
        }
1422
        
1423
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1424
        if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1425
            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1426
            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1427
            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1428
            strcpy(tempFileNameCString, tempFileTemplateCString);
1429
            int fileDescriptor = mkstemp(tempFileNameCString);
1430
            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1431
            free(tempFileNameCString);
1432
            if (fileDescriptor == -1) {
1433
                dispatch_async(dispatch_get_main_queue(), ^{
1434
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1435
                                                            message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath] 
1436
                                                              error:nil];
1437
                    [activityFacility endActivity:activity 
1438
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1439
                });
1440
                [self syncOperationFinishedWithSuccess:NO];
1441
                [pool drain];
1442
                return;
1443
            }
1444
            close(fileDescriptor);
1445
            storedState.tmpFilePath = tempFilePath;
1446
            [self saveLocalState];
1447
        }
1448
        
1449

    
1450
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1451
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1452
        [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1453
        [tempFileHandle writeData:[objectRequest responseData]];
1454
        [tempFileHandle closeFile];
1455

    
1456
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1457
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1458
        if (missingBlockIndex == NSNotFound) {
1459
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1460
            NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1461
            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1462
                dispatch_async(dispatch_get_main_queue(), ^{
1463
                    [activityFacility endActivity:activity 
1464
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1465
                });
1466
                [self syncOperationFinishedWithSuccess:NO];
1467
                [pool drain];
1468
                return;
1469
            } else if (![fileManager fileExistsAtPath:dirPath]) {
1470
                // File doesn't exist but also the containing directory doesn't exist
1471
                // In most cases this should have been resolved as an update of the corresponding local object,
1472
                // but it never hurts to check
1473
                error = nil;
1474
                [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1475
                if (error != nil) {
1476
                    dispatch_async(dispatch_get_main_queue(), ^{
1477
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1478
                                                                message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1479
                                                                  error:error];
1480
                        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1481
                                          withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1482
                    });
1483
                    [self syncOperationFinishedWithSuccess:NO];
1484
                    [pool drain];
1485
                    return;
1486
                }
1487
            }
1488
            // Move file from tmp download
1489
            error = nil;
1490
            [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1491
            if (error != nil) {
1492
                dispatch_async(dispatch_get_main_queue(), ^{
1493
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1494
                                                            message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath] 
1495
                                                              error:error];
1496
                    [activityFacility endActivity:activity 
1497
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1498
                });
1499
                [self syncOperationFinishedWithSuccess:NO];
1500
                [pool drain];
1501
                return;
1502
            }
1503
            dispatch_async(dispatch_get_main_queue(), ^{
1504
                [activityFacility endActivity:activity 
1505
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1506
                                   totalBytes:activity.totalBytes 
1507
                                 currentBytes:activity.totalBytes];
1508
            });
1509

    
1510
            storedState.filePath = filePath;
1511
            storedState.hash = object.objectHash;
1512
            storedState.tmpFilePath = nil;
1513
            [self saveLocalState];
1514
            [self syncOperationFinishedWithSuccess:YES];
1515
            [pool drain];
1516
            return;
1517
        } else {
1518
            if (newSyncRequested || syncLate || operation.isCancelled) {
1519
                [self requestFailed:objectRequest];
1520
            } else {
1521
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1522
                                                                                                       containerName:containerName 
1523
                                                                                                              object:object 
1524
                                                                                                          blockIndex:missingBlockIndex 
1525
                                                                                                           blockSize:blockSize];
1526
                newObjectRequest.delegate = self;
1527
                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1528
                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1529
                newObjectRequest.userInfo = objectRequest.userInfo;
1530
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1531
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1532
                [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1533
                    [activityFacility updateActivity:activity 
1534
                                         withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1535
                                                      object.name, 
1536
                                                      (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1537
                                          totalBytes:activity.totalBytes 
1538
                                        currentBytes:(activity.currentBytes + size)];
1539
                }];
1540
                [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1541
            }
1542
        }
1543
    } else if (objectRequest.responseStatusCode == 412) {
1544
        // The object has changed on the server
1545
        dispatch_async(dispatch_get_main_queue(), ^{
1546
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1547
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1548
        });
1549
        [self syncOperationFinishedWithSuccess:NO];
1550
    } else {
1551
        [self requestFailed:objectRequest];
1552
    }
1553
    [pool drain];
1554
}
1555

    
1556
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1557
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1558
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1559
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1560
    if (operation.isCancelled) {
1561
        [self requestFailed:objectRequest];
1562
    } else if (objectRequest.responseStatusCode == 200) {
1563
        if (newSyncRequested || syncLate || operation.isCancelled) {
1564
            [self requestFailed:objectRequest];
1565
        } else {
1566
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1567
            PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1568
            if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1569
                [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1570
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1571
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath 
1572
                                                                    blockSize:blockSize 
1573
                                                                    blockHash:blockHash 
1574
                                                                   withHashes:[objectRequest hashes]];
1575
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1576
            dispatch_async(dispatch_get_main_queue(), ^{
1577
                [activityFacility endActivity:activity 
1578
                                  withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1579
                                               object.name, 
1580
                                               (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1581
                                   totalBytes:activity.totalBytes 
1582
                                 currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1583
            });
1584

    
1585
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1586
                                                                                                   containerName:containerName 
1587
                                                                                                          object:object 
1588
                                                                                                      blockIndex:missingBlockIndex 
1589
                                                                                                       blockSize:blockSize];
1590
            newObjectRequest.delegate = self;
1591
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1592
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1593
            newObjectRequest.userInfo = objectRequest.userInfo;
1594
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1595
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1596
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1597
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1598
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1599
                [activityFacility updateActivity:activity 
1600
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1601
                                                  object.name, 
1602
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1603
                                      totalBytes:activity.totalBytes 
1604
                                    currentBytes:(activity.currentBytes + size)];
1605
            }];
1606
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1607
        }
1608
    } else {
1609
        [self requestFailed:objectRequest];
1610
    }
1611
    [pool drain];
1612
}
1613

    
1614
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1615
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1616
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1617
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1618
    if (operation.isCancelled) {
1619
        [self requestFailed:objectRequest];
1620
    } else if (objectRequest.responseStatusCode == 201) {
1621
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1622
        storedState.isDirectory = YES;
1623
        [self saveLocalState];
1624
        dispatch_async(dispatch_get_main_queue(), ^{
1625
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1626
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1627
        });
1628
        [self syncOperationFinishedWithSuccess:YES];
1629
    } else {
1630
        [self requestFailed:objectRequest];
1631
    }
1632
    [pool drain];
1633
}
1634

    
1635
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1636
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1637
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1638
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1639
    if (operation.isCancelled) {
1640
        [self requestFailed:objectRequest];
1641
    } else if (objectRequest.responseStatusCode == 201) {
1642
        [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1643
        [self saveLocalState];
1644
        dispatch_async(dispatch_get_main_queue(), ^{
1645
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1646
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1647
        });
1648
        [self syncOperationFinishedWithSuccess:YES];
1649
    } else {
1650
        [self requestFailed:objectRequest];
1651
    }
1652
    [pool drain];
1653
}
1654

    
1655
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1656
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1657
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1658
    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1659
    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1660
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1661
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1662
    NSUInteger totalBytes = activity.totalBytes;
1663
    NSUInteger currentBytes = activity.currentBytes;
1664
    if (operation.isCancelled) {
1665
        [self requestFailed:objectRequest];
1666
    } else if (objectRequest.responseStatusCode == 201) {
1667
        NSLog(@"Sync::object created: %@", objectRequest.url);
1668
        storedState.hash = [objectRequest objectHash];
1669
        [self saveLocalState];
1670
        dispatch_async(dispatch_get_main_queue(), ^{
1671
            [activityFacility endActivity:activity 
1672
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1673
                               totalBytes:totalBytes 
1674
                             currentBytes:totalBytes];
1675
        });
1676
        [self syncOperationFinishedWithSuccess:YES];
1677
    } else if (objectRequest.responseStatusCode == 409) {
1678
        if (newSyncRequested || syncLate || operation.isCancelled) {
1679
            [self requestFailed:objectRequest];
1680
        } else {
1681
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1682
            if (iteration == 0) {
1683
                NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1684
                dispatch_async(dispatch_get_main_queue(), ^{
1685
                    [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1686
                });
1687
                [self syncOperationFinishedWithSuccess:NO];
1688
                [pool drain];
1689
                return;
1690
            }
1691
            NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1692
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1693
                                                              withMissingHashes:[objectRequest hashes]];
1694
            if (totalBytes >= [missingBlocks count]*blockSize)
1695
                currentBytes = totalBytes - [missingBlocks count]*blockSize;
1696
            dispatch_async(dispatch_get_main_queue(), ^{
1697
                [activityFacility updateActivity:activity 
1698
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1699
                                      totalBytes:totalBytes 
1700
                                    currentBytes:currentBytes];
1701
            });
1702
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1703
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1704
                                                                                                             containerName:containerName 
1705
                                                                                                                 blockSize:blockSize 
1706
                                                                                                                   forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1707
                                                                                                         missingBlockIndex:missingBlockIndex 
1708
                                                                                                            sharingAccount:nil];
1709
            newContainerRequest.delegate = self;
1710
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1711
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1712
            newContainerRequest.userInfo = objectRequest.userInfo;
1713
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1714
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1715
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1716
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1717
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1718
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1719
                [activityFacility updateActivity:activity 
1720
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1721
                                      totalBytes:activity.totalBytes 
1722
                                    currentBytes:(activity.currentBytes + size)];
1723
            }];
1724
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1725
        }
1726
    } else {
1727
        [self requestFailed:objectRequest];
1728
    }
1729
    [pool drain];
1730
}
1731

    
1732
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1733
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1734
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1735
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1736
    if (operation.isCancelled) {
1737
        [self requestFailed:containerRequest];
1738
    } else if (containerRequest.responseStatusCode == 202) {
1739
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1740
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1741
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1742
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1743
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1744
        if (operation.isCancelled) {
1745
            [self requestFailed:containerRequest];
1746
        } else if (missingBlockIndex == NSNotFound) {
1747
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1748
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1749
                                                                                           containerName:containerName 
1750
                                                                                              objectName:object.name 
1751
                                                                                             contentType:object.contentType 
1752
                                                                                               blockSize:blockSize 
1753
                                                                                               blockHash:blockHash
1754
                                                                                                 forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1755
                                                                                           checkIfExists:NO 
1756
                                                                                                  hashes:&hashes 
1757
                                                                                          sharingAccount:nil];
1758
            newObjectRequest.delegate = self;
1759
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1760
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1761
            newObjectRequest.userInfo = containerRequest.userInfo;
1762
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1763
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1764
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1765
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1766
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1767
        } else {
1768
            if (newSyncRequested || syncLate || operation.isCancelled) {
1769
                [self requestFailed:containerRequest];
1770
            } else {
1771
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1772
                                                                                                                 containerName:containerName
1773
                                                                                                                     blockSize:blockSize
1774
                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1775
                                                                                                             missingBlockIndex:missingBlockIndex 
1776
                                                                                                                sharingAccount:nil];
1777
                newContainerRequest.delegate = self;
1778
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1779
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1780
                newContainerRequest.userInfo = containerRequest.userInfo;
1781
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1782
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1783
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1784
                    [activityFacility updateActivity:activity 
1785
                                         withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1786
                                          totalBytes:activity.totalBytes 
1787
                                        currentBytes:(activity.currentBytes + size)];
1788
                }];
1789
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1790
            }
1791
        }
1792
    } else {
1793
        [self requestFailed:containerRequest];
1794
    }
1795
    [pool drain];
1796
}
1797

    
1798
- (void)requestFailed:(ASIPithosRequest *)request {
1799
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1800
    NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1801
    if (operation.isCancelled) {
1802
        [pool drain];
1803
        return;        
1804
    }
1805
    if (request.isCancelled || newSyncRequested || syncLate) {
1806
        dispatch_async(dispatch_get_main_queue(), ^{
1807
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1808
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1809
        });
1810
        [self syncOperationFinishedWithSuccess:NO];
1811
        [pool drain];
1812
        return;
1813
    }
1814
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1815
    if (retries > 0) {
1816
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1817
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1818
        [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1819
    } else {
1820
        dispatch_async(dispatch_get_main_queue(), ^{
1821
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1822
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1823
        });
1824
        [self syncOperationFinishedWithSuccess:NO];
1825
    }
1826
    [pool drain];
1827
}
1828

    
1829

    
1830
@end