Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ df003f06

History | View | Annotate | Download (82.7 kB)

1
//
2
//  PithosSyncDaemon.m
3
//  pithos-macos
4
//
5
// Copyright 2011 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 "PithosLocalObjectState.h"
40
#import "PithosActivityFacility.h"
41
#import "PithosUtilities.h"
42
#import "ASINetworkQueue.h"
43
#import "ASIPithosRequest.h"
44
#import "ASIPithosContainerRequest.h"
45
#import "ASIPithosObjectRequest.h"
46
#import "ASIPithosObject.h"
47

    
48
#define DATA_MODEL_FILE @"localstate.archive"
49
#define ARCHIVE_KEY @"Data"
50

    
51
@interface PithosSyncDaemon (Private)
52
- (NSString *)pithosStateFilePath;
53
- (void)saveLocalState;
54
- (BOOL)moveToTempTrashFile:(NSString *)filePath;
55
- (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
56
- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
57
                                   object:(ASIPithosObject *)object 
58
                            localFilePath:(NSString *)filePath;
59
- (void)requestFailed:(ASIPithosRequest *)request;
60

    
61
- (void)increaseSyncOperationCount;
62
- (void)decreaseSyncOperationCount;
63

    
64
@end
65

    
66
@implementation PithosSyncDaemon
67
@synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
68
@synthesize containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
69

    
70
#pragma mark -
71
#pragma Object Lifecycle
72

    
73
- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
74
              containerName:(NSString *)aContainerName 
75
               timeInterval:(NSTimeInterval)aTimeInterval {
76
    if ((self = [super init])) {
77
        directoryPath = [aDirectoryPath copy];
78
        containerName = [aContainerName copy];
79
        timeInterval = aTimeInterval;
80
        
81
        syncOperationCount = 0;
82
        newSyncRequested = NO;
83
        containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
84
        
85
        activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
86
        
87
        if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
88
            NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
89
            NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
90
            self.storedLocalObjectStates = [unarchiver decodeObjectForKey:ARCHIVE_KEY];
91
            [unarchiver finishDecoding];
92
        } else {
93
            self.storedLocalObjectStates = [NSMutableDictionary dictionary];
94
        }
95
        
96
        queue = [[ASINetworkQueue alloc] init];
97
        queue.shouldCancelAllRequestsOnFailure = NO;
98
        [queue go];
99
        
100
        [[NSNotificationCenter defaultCenter] addObserver:self
101
                                                 selector:@selector(applicationWillTerminate:)
102
                                                     name:NSApplicationWillTerminateNotification
103
                                                   object:[NSApplication sharedApplication]];
104
    
105
        timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
106
        [timer fire];
107
    }
108
    
109
    return self;
110
}
111

    
112
- (void)dealloc {
113
    [[NSNotificationCenter defaultCenter] removeObserver:self];
114
    [queue cancelAllOperations];
115
    [queue release];
116
    [timer invalidate];
117
    [timer release];
118
    [tempTrashDirPath release];
119
    [tempDownloadsDirPath release];
120
    [pithosStateFilePath release];
121
    [containerDirectoryPath release];
122
    [currentLocalObjectStates release];
123
    [storedLocalObjectStates release];
124
    [remoteObjects release];
125
    [objects release];
126
    [lastCompletedSync release];
127
    [lastModified release];
128
    [blockHash release];
129
    [containerName release];
130
    [directoryPath release];
131
    [super dealloc];
132
}
133

    
134
#pragma mark -
135
#pragma mark Observers
136

    
137
- (void)applicationWillTerminate:(NSNotification *)notification {
138
    [self saveLocalState];
139
}
140

    
141
#pragma mark -
142
#pragma mark Properties
143

    
144
- (NSString *)pithosStateFilePath {
145
    if (!pithosStateFilePath) {
146
        pithosStateFilePath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DATA_MODEL_FILE] retain];
147
    }
148
    return [pithosStateFilePath copy];
149
}
150

    
151
- (NSString *)tempDownloadsDirPath {
152
    NSFileManager *fileManager = [NSFileManager defaultManager];
153
    if (!tempDownloadsDirPath || ![fileManager fileExistsAtPath:tempDownloadsDirPath]) {
154
        // Get the path from user defaults
155
        tempDownloadsDirPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosTempDownloadsDirPath"];
156
        if (tempDownloadsDirPath) {
157
            // Check if the path exists
158
            BOOL isDirectory;
159
            BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
160
            NSError *error = nil;
161
            if (fileExists && !isDirectory)
162
                [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
163
            if (!error & !fileExists)
164
                [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
165
            if (error)
166
                tempDownloadsDirPath = nil;
167
            }
168
        if (!tempDownloadsDirPath) {
169
            NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Downloads XXXXXX"];
170
            const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
171
            char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
172
            strcpy(tempDirNameCString, tempDirTemplateCString);
173
            tempDirNameCString = mkdtemp(tempDirNameCString);
174
            if (tempDirNameCString != NULL)
175
                tempDownloadsDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
176
            free(tempDirNameCString);
177
        }
178
        if (!tempDownloadsDirPath)
179
            [[NSUserDefaults standardUserDefaults] setObject:tempDownloadsDirPath forKey:@"PithosTempDownloadsDirPath"];
180
        [tempDownloadsDirPath retain];
181
    }
182
    return [tempDownloadsDirPath copy];
183
}
184

    
185
- (NSString *)tempTrashDirPath {
186
    NSFileManager *fileManager = [NSFileManager defaultManager];
187
    if (!tempTrashDirPath || ![fileManager fileExistsAtPath:tempTrashDirPath]) {
188
        // Get the path from user defaults
189
        tempTrashDirPath = [[NSUserDefaults standardUserDefaults] stringForKey:@"PithosTempTrashDirPath"];
190
        if (tempTrashDirPath) {
191
            // Check if the path exists
192
            BOOL isDirectory;
193
            BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
194
            NSError *error = nil;
195
            if (fileExists && !isDirectory)
196
                [fileManager removeItemAtPath:tempTrashDirPath error:&error];
197
            if (!error & !fileExists)
198
                [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
199
            if (error)
200
                tempTrashDirPath = nil;
201
        }
202
        if (!tempTrashDirPath) {
203
            NSString *tempDirTemplate = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"Temp Trash XXXXXX"];
204
            const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
205
            char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
206
            strcpy(tempDirNameCString, tempDirTemplateCString);
207
            tempDirNameCString = mkdtemp(tempDirNameCString);
208
            if (tempDirNameCString != NULL)
209
                tempTrashDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
210
            free(tempDirNameCString);
211
        }
212
        if (!tempTrashDirPath)
213
            [[NSUserDefaults standardUserDefaults] setObject:tempTrashDirPath forKey:@"PithosTempTrashDirPath"];
214
        [tempTrashDirPath retain];
215
    }
216
    return [tempTrashDirPath copy];
217
}
218

    
219
#pragma mark -
220
#pragma mark Sync
221

    
222
- (void)saveLocalState {
223
    NSMutableData *data = [NSMutableData data];
224
    NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
225
    [archiver encodeObject:storedLocalObjectStates forKey:ARCHIVE_KEY];
226
    [archiver finishEncoding];
227
    [data writeToFile:self.pithosStateFilePath atomically:YES];
228
}
229

    
230
- (void)increaseSyncOperationCount {
231
    @synchronized(self) {
232
        syncOperationCount++;
233
    }
234
}
235

    
236
- (void)decreaseSyncOperationCount {
237
    @synchronized(self) {
238
        syncOperationCount--;
239
        if (!syncOperationCount && !syncIncomplete) {
240
            self.lastCompletedSync = [NSDate date];
241
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
242
                                                  message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
243
            // The sync cycle is completed and the final operation is to move the Trash Folder the contents of the Temp Trash
244
            NSString *trashDirPath = self.tempTrashDirPath;
245
            if (tempTrashDirPath) {
246
                NSError *error = nil;
247
                NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
248
                if (error) {
249
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
250
                                                            message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
251
                                                              error:error];
252
                    return;
253
                }
254
                if ([subPaths count]) {
255
                    NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
256
                    for (NSString *subPath in subPaths) {
257
                        [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
258
                    }
259
                    syncOperationCount = 1;
260
                    [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
261
                        if (error) {
262
                            [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
263
                                                                    message:@"Cannot move files to Trash" 
264
                                                                      error:error];
265
                        }
266
                        syncOperationCount = 0;
267
                    }];
268
                }
269
            }
270
        }
271
    }
272
}
273

    
274
- (void)sync {
275
    @synchronized(self) {
276
        if (syncOperationCount) {
277
            // If at least one operation is running return
278
            newSyncRequested = YES;
279
            return;
280
        } else {
281
            // The first operation is the server listing
282
            syncOperationCount = 1;
283
            newSyncRequested = NO;
284
            syncIncomplete = NO;
285
        }
286
    }
287

    
288
    NSFileManager *fileManager = [NSFileManager defaultManager];
289
    BOOL isDirectory;
290
    NSError *error = nil;
291
    if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
292
        if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
293
            error) {
294
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
295
                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath] 
296
                                                      error:error];
297
            @synchronized(self) {
298
                syncOperationCount = 0;
299
            }
300
            return;
301
        }
302
    } else if (!isDirectory) {
303
        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
304
                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath] 
305
                                                  error:nil];
306
        @synchronized(self) {
307
            syncOperationCount = 0;
308
        }
309
        return;
310
    }
311
    
312
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
313
                                                                                                           limit:0 
314
                                                                                                          marker:nil 
315
                                                                                                          prefix:nil 
316
                                                                                                       delimiter:nil 
317
                                                                                                            path:nil 
318
                                                                                                            meta:nil 
319
                                                                                                          shared:NO 
320
                                                                                                           until:nil 
321
                                                                                                 ifModifiedSince:lastModified];
322
    containerRequest.delegate = self;
323
    containerRequest.didFinishSelector = @selector(listRequestFinished:);
324
    containerRequest.didFailSelector = @selector(listRequestFailed:);
325
    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
326
                                                               message:@"Sync: Getting server listing"];
327
    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
328
                                 activity, @"activity", 
329
                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
330
                                 @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
331
                                 @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
332
                                 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
333
                                 [NSNumber numberWithUnsignedInteger:10], @"retries", 
334
                                 nil];
335
    [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
336
}
337

    
338
- (BOOL)moveToTempTrashFile:(NSString *)filePath {
339
    NSString *trashDirPath = self.tempTrashDirPath;
340
    if (!tempTrashDirPath)
341
        return NO;
342
    NSFileManager *fileManager = [NSFileManager defaultManager];
343
    BOOL isDirectory;
344
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
345
    NSError *error = nil;
346
    NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
347
                                                                withString:trashDirPath];
348
    NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
349
    if (fileExists && isDirectory) {
350
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
351
        if (error) {
352
            [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
353
                                                    message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
354
                                                      error:error];
355
            return NO;
356
        }
357
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
358
            [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
359
                                                    message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
360
                                                      error:error];
361
            return NO;
362
        }
363
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
364
            [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
365
                                                    message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
366
                                                             filePath, newFilePath] 
367
                                                      error:error];
368
            return NO;
369
        }
370
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
371
        if (currentState) {
372
            currentState.filePath = newFilePath;
373
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
374
            [currentLocalObjectStates removeObjectForKey:filePath];        
375
        } else {
376
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
377
                                                                                       blockHash:blockHash 
378
                                                                                       blockSize:blockSize] 
379
                                         forKey:newFilePath];
380
        }
381
        for (NSString *subPath in subPaths) {
382
            NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
383
            NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
384
                                                                              withString:trashDirPath];
385
            currentState = [currentLocalObjectStates objectForKey:subFilePath];
386
            if (currentState) {
387
                currentState.filePath = newSubFilePath;
388
                [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
389
                [currentLocalObjectStates removeObjectForKey:subFilePath];
390
            } else {
391
                [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
392
                                                                                           blockHash:blockHash 
393
                                                                                           blockSize:blockSize] 
394
                                             forKey:newSubFilePath];
395
            }        
396
        }
397
    } else if (fileExists && !isDirectory) {
398
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
399
            [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
400
                                                    message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
401
                                                      error:error];
402
            return NO;
403
        }
404
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
405
            [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
406
                                                    message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
407
                                                             filePath, newFilePath] 
408
                                                      error:error];
409
            return NO;
410
        }
411
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
412
        if (currentState) {
413
            currentState.filePath = newFilePath;
414
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
415
            [currentLocalObjectStates removeObjectForKey:filePath];        
416
        } else {
417
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
418
                                                                                       blockHash:blockHash 
419
                                                                                       blockSize:blockSize] 
420
                                         forKey:newFilePath];
421
        }
422
    }
423
    return YES;
424
}
425

    
426
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
427
                     localFilePath:(NSString *)filePath {
428
    NSFileManager *fileManager = [NSFileManager defaultManager];
429
    NSError *error;
430
    BOOL isDirectory;
431
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
432
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
433
    if (!object || !object.hash) {
434
        // Delete local object
435
        // XXX move to local trash instead
436
        NSLog(@"Sync::delete local object: %@", filePath);
437
        if (!fileExists || [self moveToTempTrashFile:filePath]) {
438
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
439
                                                  message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
440
            [storedLocalObjectStates removeObjectForKey:object.name];
441
            [self saveLocalState];
442
        }
443
    } else if ([object.contentType isEqualToString:@"application/directory"]) {
444
        // Create local directory object
445
        NSLog(@"Sync::create local directory object: %@", filePath);
446
        BOOL directoryCreated = NO;
447
        if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
448
            NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
449
            error = nil;
450
            if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
451
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
452
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
453
                                                          error:error];
454
            } else {
455
                directoryCreated = YES;
456
                storedState.isDirectory = YES;
457
                [self saveLocalState];
458
            }
459
        } else {
460
            NSLog(@"Sync::local directory object exists: %@", filePath);
461
            directoryCreated = YES;
462
        }
463
        if (directoryCreated)
464
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
465
                                                  message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
466
    } else if (object.bytes == 0) {
467
        // Create local object with zero length
468
        NSLog(@"Sync::create local zero length object: %@", filePath);
469
        BOOL fileCreated = NO;
470
        if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
471
            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
472
            error = nil;
473
            if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
474
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
475
                                                        message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
476
                                                          error:error];
477
            } else {
478
                fileCreated = YES;
479
                storedState.hash = object.hash;
480
                storedState.filePath = nil;
481
                [self saveLocalState];
482
            }
483
        } else {
484
            NSLog(@"Sync::local zero length object exists: %@", filePath);
485
            fileCreated = YES;
486
        }
487
        if (fileCreated)
488
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
489
                                                  message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
490
    } else if (storedState.filePath == nil) {
491
        // Create new local object
492
        [self increaseSyncOperationCount];
493
        __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
494
                                                                                                          object:object 
495
                                                                                                      blockIndex:0 
496
                                                                                                       blockSize:blockSize];
497
        objectRequest.delegate = self;
498
        objectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
499
        objectRequest.didFailSelector = @selector(requestFailed:);
500
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
501
                                                                   message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
502
                                                                totalBytes:object.bytes 
503
                                                              currentBytes:0];
504
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
505
                                  object, @"pithosObject", 
506
                                  [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks", 
507
                                  [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
508
                                  filePath, @"filePath", 
509
                                  activity, @"activity", 
510
                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
511
                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
512
                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
513
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
514
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
515
                                  nil];
516
        [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
517
            [activityFacility updateActivity:activity 
518
                                 withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", 
519
                                              [[objectRequest.userInfo valueForKey:@"pithosObject"] name], 
520
                                              (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
521
                                  totalBytes:activity.totalBytes 
522
                                currentBytes:(activity.currentBytes + size)];
523
        }];
524
        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
525
    } else {
526
        // Resume local object download
527
        [self increaseSyncOperationCount];
528
        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName 
529
                                                                                                   objectName:object.name];
530
        objectRequest.delegate = self;
531
        objectRequest.didFinishSelector = @selector(downloadObjectHashMapFinished:);
532
        // The fail method for block download does exactly what we want
533
        objectRequest.didFailSelector = @selector(requestFailed:);
534
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
535
                                                                   message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
536
                                                                totalBytes:object.bytes 
537
                                                              currentBytes:0];
538
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
539
                                  object, @"pithosObject", 
540
                                  filePath, @"filePath", 
541
                                  activity, @"activity", 
542
                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
543
                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
544
                                  [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
545
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
546
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
547
                                  nil];
548
        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
549
    }
550
}
551

    
552
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
553
                                  object:(ASIPithosObject *)object 
554
                           localFilePath:(NSString *)filePath {
555
    [self increaseSyncOperationCount];
556
    if (currentState.isDirectory) {
557
        // Create remote directory object
558
        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
559
                                                                                                     objectName:object.name 
560
                                                                                                           eTag:nil 
561
                                                                                                    contentType:@"application/directory" 
562
                                                                                                contentEncoding:nil 
563
                                                                                             contentDisposition:nil 
564
                                                                                                       manifest:nil 
565
                                                                                                        sharing:nil 
566
                                                                                                       isPublic:ASIPithosObjectRequestPublicIgnore 
567
                                                                                                       metadata:nil 
568
                                                                                                           data:[NSData data]];
569
        objectRequest.delegate = self;
570
        objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
571
        objectRequest.didFailSelector = @selector(requestFailed:);
572
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
573
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
574
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
575
                                  object, @"pithosObject", 
576
                                  activity, @"activity", 
577
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage", 
578
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage", 
579
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage", 
580
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
581
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
582
                                  nil];
583
        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
584
    } else if (!currentState.exists) {
585
        // Delete remote object
586
        NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash" 
587
                                                                        objectName:object.name];
588
        if (safeObjectName) {
589
            ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:containerName 
590
                                                                                             objectName:object.name 
591
                                                                               destinationContainerName:@"trash" 
592
                                                                                  destinationObjectName:safeObjectName 
593
                                                                                          checkIfExists:NO];
594
            if (objectRequest) {
595
                objectRequest.delegate = self;
596
                objectRequest.didFinishSelector = @selector(moveObjectToTrashFinished:);
597
                objectRequest.didFailSelector = @selector(requestFailed:);
598
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
599
                                                                           message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
600
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
601
                                          object, @"pithosObject", 
602
                                          activity, @"activity", 
603
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage", 
604
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage", 
605
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage", 
606
                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
607
                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
608
                                          nil];
609
                [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
610
            } else {
611
                syncIncomplete = YES;
612
                [self decreaseSyncOperationCount];
613
            }
614
        } else {
615
            syncIncomplete = YES;
616
            [self decreaseSyncOperationCount];
617
        }
618
    } else {
619
        // Upload file to remote object
620
        NSError *error = nil;
621
        object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
622
        if (object.contentType == nil)
623
            object.contentType = @"application/octet-stream";
624
        if (error)
625
            NSLog(@"contentType detection error: %@", error);
626
        NSArray *hashes = nil;
627
        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
628
                                                                                              objectName:object.name 
629
                                                                                             contentType:object.contentType 
630
                                                                                               blockSize:blockSize 
631
                                                                                               blockHash:blockHash 
632
                                                                                                 forFile:filePath 
633
                                                                                           checkIfExists:NO 
634
                                                                                                  hashes:&hashes 
635
                                                                                          sharingAccount:nil];
636
        if (objectRequest) {
637
            objectRequest.delegate = self;
638
            objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
639
            objectRequest.didFailSelector = @selector(requestFailed:);
640
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
641
                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
642
                                                                    totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
643
                                                                  currentBytes:0];
644
            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
645
             [NSDictionary dictionaryWithObjectsAndKeys:
646
              object, @"pithosObject", 
647
              filePath, @"filePath", 
648
              hashes, @"hashes", 
649
              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
650
              activity, @"activity", 
651
              [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
652
              [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
653
              [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
654
              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
655
              [NSNumber numberWithUnsignedInteger:10], @"retries", 
656
              nil]];
657
            [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
658
        } else {
659
            syncIncomplete = YES;
660
            [self decreaseSyncOperationCount];
661
        }
662
    }
663

    
664
}
665

    
666
#pragma mark -
667
#pragma mark ASIHTTPRequestDelegate
668

    
669
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
670
    NSLog(@"Sync::list request finished: %@", containerRequest.url);
671
    if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
672
        if (containerRequest.responseStatusCode == 200) {
673
            NSArray *someObjects = [containerRequest objects];
674
            if (objects == nil) {
675
                objects = [[NSMutableArray alloc] initWithArray:someObjects];
676
            } else {
677
                [objects addObjectsFromArray:someObjects];
678
            }
679
            if ([someObjects count] < 10000) {
680
                self.blockHash = [containerRequest blockHash];
681
                self.blockSize = [containerRequest blockSize];
682
                self.lastModified = [containerRequest lastModified];
683
                self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
684
                for (ASIPithosObject *object in objects) {
685
                    [remoteObjects setObject:object forKey:object.name];
686
                }
687
                [objects release];
688
                objects = nil;
689
            } else {
690
                // Do an additional request to fetch more objects
691
                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
692
                                                                                                                          limit:0 
693
                                                                                                                         marker:[[someObjects lastObject] name] 
694
                                                                                                                         prefix:nil 
695
                                                                                                                      delimiter:nil 
696
                                                                                                                           path:nil 
697
                                                                                                                           meta:nil 
698
                                                                                                                         shared:NO 
699
                                                                                                                          until:nil 
700
                                                                                                                ifModifiedSince:lastModified];
701
                newContainerRequest.delegate = self;
702
                newContainerRequest.didFinishSelector = @selector(listRequestFinished:);
703
                newContainerRequest.didFailSelector = @selector(listRequestFailed:);
704
                newContainerRequest.userInfo = newContainerRequest.userInfo;
705
                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
706
                [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
707
                return;
708
            }
709
        }
710
        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
711
                          withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
712
        NSFileManager *fileManager = [NSFileManager defaultManager];
713
        NSError *error = nil;
714
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
715
        if (error) {
716
            [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
717
                                                    message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
718
                                                      error:error];
719
            [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
720
            @synchronized(self) {
721
                // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccesfully
722
                syncOperationCount = 0;
723
                if (newSyncRequested)
724
                    [self sync];
725
            }
726
            return;
727
        }
728
        self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
729
        for (NSString *objectName in subPaths) {
730
            if (![storedLocalObjectStates objectForKey:objectName]) {
731
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
732
            }
733
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
734
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
735
                                                                                       blockHash:blockHash 
736
                                                                                       blockSize:blockSize] 
737
                                         forKey:filePath];
738
        }
739
        [self saveLocalState];
740

    
741
        for (NSString *objectName in remoteObjects) {
742
            if (![storedLocalObjectStates objectForKey:objectName])
743
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
744
        }
745

    
746
        for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
747
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
748
            if ([objectName hasSuffix:@"/"])
749
                filePath = [filePath stringByAppendingString:@":"];
750
            ASIPithosObject *object = [ASIPithosObject object];
751
            object.name = objectName;
752
            NSLog(@"Sync::object name: %@", objectName);
753
            
754
            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
755
            PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
756
            if (!currentLocalObjectState)
757
                currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
758
                                                                                 blockHash:blockHash 
759
                                                                                 blockSize:blockSize];
760
            if (currentLocalObjectState.isDirectory)
761
                object.contentType = @"application/directory";
762
            
763
            PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
764
            ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
765
            if (remoteObject) {
766
                if ([remoteObject.contentType isEqualToString:@"application/directory"]) {
767
                    remoteObjectState.isDirectory = YES;
768
                    object.contentType = @"application/directory";
769
                } else {
770
                    remoteObjectState.hash = remoteObject.hash;
771
                }
772
            }
773

    
774
            BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
775
            BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
776
            NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
777
            // XXX shouldn't we first do all the deletes? in order not to face a dir that becomes a file and vice versa
778
            if (!localStateHasChanged) {
779
                // Local state hasn't changed
780
                if (serverStateHasChanged) {
781
                    // Server state has changed
782
                    // Update local state to match that of the server 
783
                    object.bytes = remoteObject.bytes;
784
                    object.version = remoteObject.version;
785
                    object.contentType = remoteObject.contentType;
786
                    object.hash = remoteObject.hash;
787
                    [self updateLocalStateWithObject:object localFilePath:filePath];
788
                } else if (!remoteObject && !currentLocalObjectState.exists) {
789
                    // Server state hasn't changed
790
                    // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
791
                    [storedLocalObjectStates removeObjectForKey:objectName];
792
                    [self saveLocalState];
793
                }
794
            } else {
795
                // Local state has changed
796
                if (!serverStateHasChanged) {
797
                    // Server state hasn't changed
798
                    [self updateServerStateWithCurrentState:currentLocalObjectState 
799
                                                     object:object 
800
                                              localFilePath:filePath];
801
                } else {
802
                    // Server state has also changed
803
                    if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
804
                        // Both did the same change (directory)
805
                        storedLocalObjectState.isDirectory = YES;
806
                        [self saveLocalState];
807
                    } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
808
                        // Both did the same change (object edit or delete)
809
                        if (!remoteObjectState.exists)
810
                            [storedLocalObjectStates removeObjectForKey:object.name];
811
                        else
812
                            storedLocalObjectState.hash = remoteObjectState.hash;
813
                        [self saveLocalState];
814
                    } else {
815
                        // Conflict, we ask the user which change to keep
816
                        NSString *informativeText;
817
                        NSString *firstButtonText;
818
                        NSString *secondButtonText;
819
                        
820
                        if (!remoteObjectState.exists) {
821
                            // Remote object has been deleted
822
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
823
                            firstButtonText = @"Delete local file";
824
                            secondButtonText = @"Upload file to server";
825
                        } else if (!currentLocalObjectState.exists) {
826
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
827
                            firstButtonText = @"Download file from server";
828
                            secondButtonText = @"Delete file on server";
829
                        } else {
830
                            informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
831
                            firstButtonText = @"Keep server version";
832
                            secondButtonText = @"Keep local version";
833
                        }
834
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
835
                        [alert setMessageText:@"Conflict"];
836
                        [alert setInformativeText:informativeText];
837
                        [alert addButtonWithTitle:firstButtonText];
838
                        [alert addButtonWithTitle:secondButtonText];
839
                        [alert addButtonWithTitle:@"Do nothing"];
840
                        NSInteger choice = [alert runModal];
841
                        if (choice == NSAlertFirstButtonReturn) {
842
                            object.bytes = remoteObject.bytes;
843
                            object.version = remoteObject.version;
844
                            object.contentType = remoteObject.contentType;
845
                            object.hash = remoteObject.hash;
846
                            [self updateLocalStateWithObject:object localFilePath:filePath];
847
                        } if (choice == NSAlertSecondButtonReturn) {
848
                            [self updateServerStateWithCurrentState:currentLocalObjectState 
849
                                                             object:object 
850
                                                      localFilePath:filePath];
851
                        }
852
                    }
853
                }
854
            }
855
        }
856
        @synchronized(self) {
857
            [self decreaseSyncOperationCount];
858
            if (newSyncRequested && !syncOperationCount)
859
                [self sync];
860
        }
861
    } else {
862
        NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
863
        if (retries > 0) {
864
            ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
865
            [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
866
            [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
867
        } else {
868
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
869
                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
870
            @synchronized(self) {
871
                // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
872
                syncOperationCount = 0;
873
                if (newSyncRequested)
874
                    [self sync];
875
            }
876
        }
877
    }
878
}
879

    
880
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
881
    if ([containerRequest isCancelled]) {
882
        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
883
                          withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
884
        [objects release];
885
        objects = nil;
886
        @synchronized(self) {
887
            syncOperationCount = 0;
888
        }
889
        return;
890
    }
891
    // If the server listing fails, the sync should start over, so just retrying is enough
892
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
893
    if (retries > 0) {
894
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
895
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
896
        [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
897
    } else {
898
        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
899
                          withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
900
        [objects release];
901
        objects = nil;
902
        @synchronized(self) {
903
            // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
904
            syncOperationCount = 0;
905
            if (newSyncRequested)
906
                [self sync];
907
        }
908
    }
909
}
910

    
911
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
912
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
913
    if (objectRequest.responseStatusCode == 206) {
914
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
915
        NSFileManager *fileManager = [NSFileManager defaultManager];
916
        NSError *error;
917
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
918
        
919
        NSString *downloadsDirPath = self.tempDownloadsDirPath;
920
        if (!downloadsDirPath) {
921
            [activityFacility endActivity:activity 
922
                              withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
923
            @synchronized(self) {
924
                syncIncomplete = YES;
925
                [self decreaseSyncOperationCount];
926
                if (newSyncRequested && !syncOperationCount)
927
                    [self sync];
928
            }
929
            return;
930
        }
931
        
932
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
933
        if ((storedState.filePath == nil) || ![fileManager fileExistsAtPath:storedState.filePath]) {
934
            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
935
            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
936
            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
937
            strcpy(tempFileNameCString, tempFileTemplateCString);
938
            int fileDescriptor = mkstemp(tempFileNameCString);
939
            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
940
            free(tempFileNameCString);
941
            if (fileDescriptor == -1) {
942
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
943
                                                        message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", storedState.filePath] 
944
                                                          error:nil];
945
                [activityFacility endActivity:activity 
946
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
947
                @synchronized(self) {
948
                    syncIncomplete = YES;
949
                    [self decreaseSyncOperationCount];
950
                    if (newSyncRequested && !syncOperationCount)
951
                        [self sync];
952
                }
953
                return;
954
            }
955
            close(fileDescriptor);
956
            storedState.filePath = tempFilePath;
957
            [self saveLocalState];
958
        }
959
        
960

    
961
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
962
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.filePath];
963
        [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
964
        [tempFileHandle writeData:[objectRequest responseData]];
965
        [tempFileHandle closeFile];
966

    
967
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
968
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
969
        if (missingBlockIndex == NSNotFound) {
970
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
971
            NSString *dirPath = [filePath stringByDeletingLastPathComponent];
972
            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
973
                [activityFacility endActivity:activity 
974
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
975
                @synchronized(self) {
976
                    syncIncomplete = YES;
977
                    [self decreaseSyncOperationCount];
978
                    if (newSyncRequested && !syncOperationCount)
979
                        [self sync];
980
                }
981
                return;
982
            } else if (![fileManager fileExistsAtPath:dirPath]) {
983
                // File doesn't exist but also the containing directory doesn't exist
984
                // In most cases this should have been resolved as an update of the corresponding local object,
985
                // but it never hurts to check
986
                error = nil;
987
                [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
988
                if (error != nil) {
989
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
990
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
991
                                                              error:error];
992
                    [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
993
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
994
                    @synchronized(self) {
995
                        syncIncomplete = YES;
996
                        [self decreaseSyncOperationCount];
997
                        if (newSyncRequested && !syncOperationCount)
998
                            [self sync];
999
                    }
1000
                    return;
1001
                }
1002
            }
1003
            // Move file from tmp download
1004
            error = nil;
1005
            [fileManager moveItemAtPath:storedState.filePath toPath:filePath error:&error];
1006
            if (error != nil) {
1007
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1008
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.filePath, filePath] 
1009
                                                          error:error];
1010
                [activityFacility endActivity:activity 
1011
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1012
                @synchronized(self) {
1013
                    syncIncomplete = YES;
1014
                    [self decreaseSyncOperationCount];
1015
                    if (newSyncRequested && !syncOperationCount)
1016
                        [self sync];
1017
                }
1018
                return;
1019
            }
1020
            [activityFacility endActivity:activity 
1021
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1022
                               totalBytes:activity.totalBytes 
1023
                             currentBytes:activity.totalBytes];
1024

    
1025
            storedState.hash = object.hash;
1026
            storedState.filePath = nil;
1027
            [self saveLocalState];
1028
            
1029
            @synchronized(self) {
1030
                [self decreaseSyncOperationCount];
1031
                if (newSyncRequested && !syncOperationCount)
1032
                    [self sync];
1033
            }
1034
            return;
1035
        } else {
1036
            if (newSyncRequested) {
1037
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1038
                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1039
                @synchronized(self) {
1040
                    syncIncomplete = YES;
1041
                    [self decreaseSyncOperationCount];
1042
                    if (!syncOperationCount)
1043
                        [self sync];
1044
                }
1045
                return;
1046
            } else {
1047
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1048
                                                                                                                     object:object 
1049
                                                                                                                 blockIndex:missingBlockIndex 
1050
                                                                                                                  blockSize:blockSize];
1051
                newObjectRequest.delegate = self;
1052
                newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1053
                newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1054
                newObjectRequest.userInfo = objectRequest.userInfo;
1055
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1056
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1057
                [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1058
                    [activityFacility updateActivity:activity 
1059
                                         withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", 
1060
                                                      object.name, 
1061
                                                      (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1062
                                          totalBytes:activity.totalBytes 
1063
                                        currentBytes:(activity.currentBytes + size)];
1064
                }];
1065
                [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1066
            }
1067
        }
1068
    } else if (objectRequest.responseStatusCode == 412) {
1069
        // The object has changed on the server
1070
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1071
                          withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1072
        @synchronized(self) {
1073
            syncIncomplete = YES;
1074
            [self decreaseSyncOperationCount];
1075
            if (newSyncRequested && !syncOperationCount)
1076
                [self sync];
1077
        }
1078
    } else {
1079
        [self requestFailed:objectRequest];
1080
    }
1081
}
1082

    
1083
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1084
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1085
    if (objectRequest.responseStatusCode == 200) {
1086
        if (newSyncRequested) {
1087
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1088
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1089
            @synchronized(self) {
1090
                syncIncomplete = YES;
1091
                [self decreaseSyncOperationCount];
1092
                if (!syncOperationCount)
1093
                    [self sync];
1094
            }
1095
            return;
1096
        } else {
1097
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1098
            PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1099
            if ([PithosUtilities bytesOfFile:storedState.filePath] > object.bytes)
1100
                [[NSFileHandle fileHandleForWritingAtPath:storedState.filePath] truncateFileAtOffset:object.bytes];
1101
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1102
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.filePath 
1103
                                                                    blockSize:blockSize 
1104
                                                                    blockHash:blockHash 
1105
                                                                   withHashes:[objectRequest hashes]];
1106
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1107
            [activityFacility endActivity:activity 
1108
                              withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1109
                                           object.name, 
1110
                                           (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1111
                               totalBytes:activity.totalBytes 
1112
                             currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1113

    
1114
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1115
                                                                                                                 object:object 
1116
                                                                                                             blockIndex:missingBlockIndex 
1117
                                                                                                              blockSize:blockSize];
1118
            newObjectRequest.delegate = self;
1119
            newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1120
            newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1121
            newObjectRequest.userInfo = objectRequest.userInfo;
1122
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1123
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1124
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1125
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1126
                [activityFacility updateActivity:activity 
1127
                                     withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", 
1128
                                                  object.name, 
1129
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1130
                                      totalBytes:activity.totalBytes 
1131
                                    currentBytes:(activity.currentBytes + size)];
1132
            }];
1133
            [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1134
        }
1135
    } else {
1136
        [self requestFailed:objectRequest];
1137
    }
1138
}
1139

    
1140
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1141
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1142
    if (objectRequest.responseStatusCode == 201) {
1143
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1144
        storedState.isDirectory = YES;
1145
        [self saveLocalState];
1146
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1147
                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1148
        @synchronized(self) {
1149
            [self decreaseSyncOperationCount];
1150
            if (newSyncRequested && !syncOperationCount)
1151
                [self sync];
1152
        }
1153
    } else {
1154
        [self requestFailed:objectRequest];
1155
    }
1156
}
1157

    
1158
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1159
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1160
    if (objectRequest.responseStatusCode == 201) {
1161
        [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1162
        [self saveLocalState];
1163
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1164
                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1165
        @synchronized(self) {
1166
            [self decreaseSyncOperationCount];
1167
            if (newSyncRequested && !syncOperationCount)
1168
                [self sync];
1169
        }
1170
    } else {
1171
        [self requestFailed:objectRequest];
1172
    }
1173
}
1174

    
1175
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1176
    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1177
    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1178
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1179
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1180
    NSUInteger totalBytes = activity.totalBytes;
1181
    NSUInteger currentBytes = activity.currentBytes;
1182
    if (objectRequest.responseStatusCode == 201) {
1183
        NSLog(@"Sync::object created: %@", objectRequest.url);
1184
        storedState.hash = [objectRequest eTag];
1185
        [self saveLocalState];
1186
        [activityFacility endActivity:activity 
1187
                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1188
                           totalBytes:totalBytes 
1189
                         currentBytes:totalBytes];
1190
        @synchronized(self) {
1191
            [self decreaseSyncOperationCount];
1192
            if (newSyncRequested && !syncOperationCount)
1193
                [self sync];
1194
        }
1195
    } else if (objectRequest.responseStatusCode == 409) {
1196
        if (newSyncRequested) {
1197
            [activityFacility endActivity:activity 
1198
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1199
            @synchronized(self) {
1200
                syncIncomplete = YES;
1201
                [self decreaseSyncOperationCount];
1202
                if (!syncOperationCount)
1203
                    [self sync];
1204
            }
1205
            return;
1206
        } else {
1207
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1208
            if (iteration == 0) {
1209
                NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1210
                [activityFacility endActivity:activity 
1211
                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1212
                syncIncomplete = YES;
1213
                [self decreaseSyncOperationCount];
1214
                return;
1215
            }
1216
            NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1217
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1218
                                                      withMissingHashesResponse:[objectRequest responseString]];
1219
            if (totalBytes >= [missingBlocks count]*blockSize)
1220
                currentBytes = totalBytes - [missingBlocks count]*blockSize;
1221
            [activityFacility updateActivity:activity 
1222
                                 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1223
                                  totalBytes:totalBytes 
1224
                                currentBytes:currentBytes];
1225
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1226
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName 
1227
                                                                                                                        blockSize:blockSize 
1228
                                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1229
                                                                                                                missingBlockIndex:missingBlockIndex 
1230
                                                                                                                   sharingAccount:nil];
1231
            newContainerRequest.delegate = self;
1232
            newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1233
            newContainerRequest.didFailSelector = @selector(requestFailed:);
1234
            newContainerRequest.userInfo = objectRequest.userInfo;
1235
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1236
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1237
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1238
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1239
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1240
                [activityFacility updateActivity:activity 
1241
                                     withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1242
                                      totalBytes:activity.totalBytes 
1243
                                    currentBytes:(activity.currentBytes + size)];
1244
            }];
1245
            [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1246
        }
1247
    } else {
1248
        [self requestFailed:objectRequest];
1249
    }
1250
}
1251

    
1252
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1253
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1254
    if (containerRequest.responseStatusCode == 202) {
1255
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1256
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1257
        NSUInteger totalBytes = activity.totalBytes;
1258
        NSUInteger currentBytes = activity.currentBytes + blockSize;
1259
        if (currentBytes > totalBytes)
1260
            currentBytes = totalBytes;
1261
        [activityFacility updateActivity:activity 
1262
                             withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1263
                              totalBytes:totalBytes 
1264
                            currentBytes:currentBytes];
1265
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1266
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1267
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1268
        if (missingBlockIndex == NSNotFound) {
1269
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1270
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
1271
                                                                                                     objectName:object.name 
1272
                                                                                                    contentType:object.contentType 
1273
                                                                                                      blockSize:blockSize 
1274
                                                                                                      blockHash:blockHash
1275
                                                                                                        forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1276
                                                                                                  checkIfExists:NO 
1277
                                                                                                         hashes:&hashes 
1278
                                                                                                 sharingAccount:nil];
1279
            newObjectRequest.delegate = self;
1280
            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1281
            newObjectRequest.didFailSelector = @selector(requestFailed:);
1282
            newObjectRequest.userInfo = containerRequest.userInfo;
1283
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1284
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1285
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1286
            [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1287
        } else {
1288
            if (newSyncRequested) {
1289
                [activityFacility endActivity:activity 
1290
                                  withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1291
                @synchronized(self) {
1292
                    syncIncomplete = YES;
1293
                    [self decreaseSyncOperationCount];
1294
                    if (!syncOperationCount)
1295
                        [self sync];
1296
                }
1297
                return;
1298
            } else {
1299
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1300
                                                                                                                            blockSize:blockSize
1301
                                                                                                                              forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1302
                                                                                                                    missingBlockIndex:missingBlockIndex 
1303
                                                                                                                       sharingAccount:nil];
1304
                newContainerRequest.delegate = self;
1305
                newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1306
                newContainerRequest.didFailSelector = @selector(requestFailed:);
1307
                newContainerRequest.userInfo = containerRequest.userInfo;
1308
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1309
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1310
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1311
                    [activityFacility updateActivity:activity 
1312
                                         withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1313
                                          totalBytes:activity.totalBytes 
1314
                                        currentBytes:(activity.currentBytes + size)];
1315
                }];
1316
                [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1317
            }
1318
        }
1319
    } else {
1320
        [self requestFailed:containerRequest];
1321
    }
1322
}
1323

    
1324
- (void)requestFailed:(ASIPithosRequest *)request {
1325
    if ([request isCancelled]) {
1326
        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1327
                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1328
        syncIncomplete = YES;
1329
        [self decreaseSyncOperationCount];
1330
        return;
1331
    }
1332
    if (newSyncRequested) {
1333
        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1334
                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1335
        @synchronized(self) {
1336
            syncIncomplete = YES;
1337
            [self decreaseSyncOperationCount];
1338
            if (!syncOperationCount)
1339
                [self sync];
1340
        }
1341
        return;
1342
    }
1343
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1344
    if (retries > 0) {
1345
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1346
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1347
        [queue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1348
    } else {
1349
        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1350
                          withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1351
        syncIncomplete = YES;
1352
        [self decreaseSyncOperationCount];
1353
    }
1354
}
1355

    
1356

    
1357
@end