Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ e8dc9335

History | View | Annotate | Download (132.7 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 "ASIPithosContainer.h"
47
#import "ASIPithosContainerRequest.h"
48
#import "ASIPithosObjectRequest.h"
49
#import "ASIPithosObject.h"
50

    
51
@interface PithosSyncDaemon (Private)
52
- (void)loadLocalState;
53
- (void)resetLocalStateWithAll:(BOOL)all;
54
- (void)saveLocalState;
55

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

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

    
70
- (void)syncOperationStarted;
71
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull;
72

    
73
@end
74

    
75
@implementation PithosSyncDaemon
76
@synthesize directoryPath, accountsDictionary, pithos;
77
@synthesize containersDictionary, pithosContainers;
78
@synthesize lastCompletedSync, remoteObjects, previousRemoteObjects, storedLocalObjectStates, currentLocalObjectStates;
79
@synthesize pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
80

    
81
#pragma mark -
82
#pragma Object Lifecycle
83

    
84
- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
85
              pithosAccount:(PithosAccount *)aPithosAccount 
86
         accountsDictionary:(NSDictionary *)anAccountsDictionary 
87
            resetLocalState:(BOOL)resetLocalState {
88
    if ((self = [super init])) {
89
        directoryPath = [aDirectoryPath copy];
90
        pithosAccount = [aPithosAccount retain];
91
        accountsDictionary = [anAccountsDictionary copy];
92
        self.pithos = pithosAccount.pithos;
93
        
94
        self.containersDictionary = [accountsDictionary objectForKey:@""];
95
        if (!containersDictionary)
96
            self.containersDictionary = [NSDictionary dictionary];
97
        containersCount = [containersDictionary count];
98
        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
99
        for (NSString *containerName in containersDictionary) {
100
            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
101
            pithosContainer.name = containerName;
102
            [pithosContainers addObject:pithosContainer];
103
        }
104
        
105
        activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
106
        
107
        if (resetLocalState)
108
            [self resetLocalStateWithAll:YES];
109
        else
110
            [self resetLocalStateWithAll:NO];
111
        
112
        networkQueue = [[ASINetworkQueue alloc] init];
113
        networkQueue.showAccurateProgress = YES;
114
        networkQueue.shouldCancelAllRequestsOnFailure = NO;
115
//        networkQueue.maxConcurrentOperationCount = 1;
116
        
117
        callbackQueue = [[NSOperationQueue alloc] init];
118
        [callbackQueue setSuspended:YES];
119
        callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
120
//        callbackQueue.maxConcurrentOperationCount = 1;
121
        
122
        [[NSNotificationCenter defaultCenter] addObserver:self
123
                                                 selector:@selector(applicationWillTerminate:)
124
                                                     name:NSApplicationWillTerminateNotification
125
                                                   object:[NSApplication sharedApplication]];
126
    }
127
    return self;
128
}
129

    
130
- (void)loadLocalState {
131
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
132
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath])
133
        self.storedLocalObjectStates = [NSKeyedUnarchiver unarchiveObjectWithFile:self.pithosStateFilePath];
134
    else
135
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
136
    if (!storedLocalObjectStates)
137
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
138
    for (ASIPithosContainer *pithosContainer in pithosContainers) {
139
        if (![storedLocalObjectStates objectForKey:pithosContainer.name]) {
140
            [storedLocalObjectStates setObject:[NSMutableDictionary dictionary] forKey:pithosContainer.name];
141
        }
142
    }
143
    [pool drain];
144
}
145

    
146
- (void)resetLocalStateWithAll:(BOOL)all {
147
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
148
    self.lastCompletedSync = nil;
149
    NSFileManager *fileManager = [NSFileManager defaultManager];
150
    NSError *error;
151
    if (all) {
152
        self.storedLocalObjectStates = [NSMutableDictionary dictionary];
153
        [self saveLocalState]; // Save an empty dictionary
154
        [self loadLocalState]; // Load to populate with containers
155
        [self saveLocalState]; // Save again
156
        if (self.tempDownloadsDirPath) {
157
            error = nil;
158
            for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
159
                if (error) {
160
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
161
                                                            message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
162
                                                              error:error];
163
                    break;
164
                }
165
                NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
166
                if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
167
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
168
                                                            message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
169
                                                              error:error];
170
                }
171
                error = nil;
172
            }
173
        }
174
    } else {
175
        // Remove containers that don't interest us anymore and save
176
        if (!storedLocalObjectStates)
177
            [self loadLocalState];
178
        for (NSString *containerName in storedLocalObjectStates) {
179
            if (![containersDictionary objectForKey:containerName]) {
180
                [storedLocalObjectStates removeObjectForKey:containerName];
181
                if (self.tempDownloadsDirPath) {
182
                    NSString *containerTempDownloadsDirPath = [self.tempDownloadsDirPath stringByAppendingPathComponent:containerName];
183
                    BOOL isDirectory;
184
                    BOOL fileExists = [fileManager fileExistsAtPath:containerTempDownloadsDirPath isDirectory:&isDirectory];
185
                    if (fileExists && isDirectory) {
186
                        error = nil;
187
                        for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:containerTempDownloadsDirPath error:&error]) {
188
                            if (error) {
189
                                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
190
                                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", 
191
                                                                                 containerTempDownloadsDirPath] 
192
                                                                          error:error];
193
                                break;
194
                            }
195
                            NSString *subFilePath = [containerTempDownloadsDirPath stringByAppendingPathComponent:subPath];
196
                            if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
197
                                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
198
                                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", 
199
                                                                                 subFilePath] 
200
                                                                          error:error];
201
                            }
202
                            error = nil;
203
                        }
204
                    } else if (fileExists && !isDirectory) {
205
                        error = nil;
206
                        if (![fileManager removeItemAtPath:containerTempDownloadsDirPath error:&error] || error) {
207
                            [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
208
                                                                    message:[NSString stringWithFormat:@"Cannot remove file at '%@'", 
209
                                                                             containerTempDownloadsDirPath] 
210
                                                                      error:error];
211
                        }
212
                    }
213
                }
214
            }
215
        }
216
        [self saveLocalState];
217
    }
218
    [pool drain];
219
}
220

    
221
- (void)saveLocalState {
222
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
223
    [NSKeyedArchiver archiveRootObject:storedLocalObjectStates toFile:self.pithosStateFilePath];
224
    [pool drain];
225
}
226

    
227
- (void)resetDaemon {
228
    @synchronized(self) {
229
        if (!daemonActive)
230
            return;
231
    }
232
    
233
    [networkQueue reset];
234
    [callbackQueue cancelAllOperations];
235
    [callbackQueue setSuspended:YES];
236
    [self emptyTempTrash];
237
    
238
    syncOperationCount = 0;
239
    
240
    @synchronized(self) {            
241
        daemonActive = NO;
242
    }
243
}
244

    
245
- (void)startDaemon {
246
    @synchronized(self) {
247
        if (daemonActive)
248
            return;
249
    }
250

    
251
    // In the improbable case of leftover operations
252
    [networkQueue reset];
253
    [callbackQueue cancelAllOperations];
254
    
255
    syncOperationCount = 0;
256
    newSyncRequested = NO;
257
    syncIncomplete = NO;
258
    syncLate = NO;
259
    
260
    [self loadLocalState];
261
    
262
    [networkQueue go];
263
    [callbackQueue setSuspended:NO];
264
    
265
    @synchronized(self) {
266
        daemonActive = YES;
267
    }
268
}
269

    
270
- (void)dealloc {
271
    [[NSNotificationCenter defaultCenter] removeObserver:self];
272
    [self resetDaemon];
273
    [callbackQueue release];
274
    [networkQueue release];
275
    [tempTrashDirPath release];
276
    [tempDownloadsDirPath release];
277
    [pithosStateFilePath release];
278
    [currentLocalObjectStates release];
279
    [storedLocalObjectStates release];
280
    [previousRemoteObjects release];
281
    [remoteObjects release];
282
    [objects release];
283
    [lastCompletedSync release];
284
    [pithosContainers release];
285
    [containersDictionary release];
286
    [pithos release];
287
    [accountsDictionary release];
288
    [pithosAccount release];
289
    [directoryPath release];
290
    [super dealloc];
291
}
292

    
293
#pragma mark -
294
#pragma mark Observers
295

    
296
- (void)applicationWillTerminate:(NSNotification *)notification {
297
    [self saveLocalState];
298
}
299

    
300
#pragma mark -
301
#pragma mark Properties
302

    
303
- (NSString *)pithosStateFilePath {
304
    if (!pithosStateFilePath) {
305
        pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
306
                                 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
307
                                stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-PithosLocalObjectStates.archive", 
308
                                                                pithosAccount.uniqueName]] retain];
309
        NSString *pithosStateFileDirPath = [pithosStateFilePath stringByDeletingLastPathComponent];
310
        NSFileManager *fileManager = [NSFileManager defaultManager];
311
        BOOL isDirectory;
312
        BOOL fileExists = [fileManager fileExistsAtPath:pithosStateFileDirPath isDirectory:&isDirectory];
313
        NSError *error = nil;
314
        if (fileExists && !isDirectory)
315
            [fileManager removeItemAtPath:pithosStateFileDirPath error:&error];
316
        if (!error && !fileExists)
317
            [fileManager createDirectoryAtPath:pithosStateFileDirPath withIntermediateDirectories:YES attributes:nil error:&error];
318
        //if (error)
319
        //  pithosStateFilePath = nil;
320
        // XXX create a dir using mktmps?
321
    }
322
    return [[pithosStateFilePath copy] autorelease];
323
}
324

    
325
- (NSString *)tempDownloadsDirPath {
326
    if (!tempDownloadsDirPath) {
327
        tempDownloadsDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
328
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
329
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempDownloads", 
330
                                                                 pithosAccount.uniqueName]] retain];
331
        NSFileManager *fileManager = [NSFileManager defaultManager];
332
        BOOL isDirectory;
333
        BOOL fileExists = [fileManager fileExistsAtPath:tempDownloadsDirPath isDirectory:&isDirectory];
334
        NSError *error = nil;
335
        if (fileExists && !isDirectory)
336
            [fileManager removeItemAtPath:tempDownloadsDirPath error:&error];
337
        if (!error && !fileExists)
338
            [fileManager createDirectoryAtPath:tempDownloadsDirPath withIntermediateDirectories:YES attributes:nil error:&error];
339
        //if (error)
340
        //    tempDownloadsDirPath = nil;
341
        // XXX create a dir using mktmps?
342
    }
343
    return tempDownloadsDirPath;    
344
}
345

    
346
- (NSString *)tempTrashDirPath {
347
    if (!tempTrashDirPath) {
348
        tempTrashDirPath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
349
                                  stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
350
                                 stringByAppendingPathComponent:[NSString stringWithFormat:@"%@-TempTrash", 
351
                                                                 pithosAccount.uniqueName]] retain];
352
        NSFileManager *fileManager = [NSFileManager defaultManager];
353
        BOOL isDirectory;
354
        BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
355
        NSError *error = nil;
356
        if (fileExists && !isDirectory)
357
            [fileManager removeItemAtPath:tempTrashDirPath error:&error];
358
        if (!error && !fileExists)
359
            [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
360
        //if (error)
361
        //    tempTrashDirPath = nil;
362
        // XXX create a dir using mktmps?
363
    }
364
    return tempTrashDirPath;
365
}
366

    
367
- (void)setDirectoryPath:(NSString *)aDirectoryPath {
368
    if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
369
        [self resetDaemon];
370
        [self resetLocalStateWithAll:YES];
371
        [directoryPath release];
372
        directoryPath = [aDirectoryPath copy];
373
    }
374
}
375

    
376
- (void)setAccountsDictionary:(NSDictionary *)anAccountsDictionary {
377
    if (anAccountsDictionary && ![anAccountsDictionary isEqualToDictionary:accountsDictionary]) {
378
        [self resetDaemon];
379
        [accountsDictionary release];
380
        accountsDictionary = [anAccountsDictionary copy];
381
        
382
        self.containersDictionary = [accountsDictionary objectForKey:@""];
383
        if (!containersDictionary)
384
            self.containersDictionary = [NSDictionary dictionary];
385
        containersCount = [containersDictionary count];
386
        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
387
        for (NSString *containerName in containersDictionary) {
388
            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
389
            pithosContainer.name = containerName;
390
            [pithosContainers addObject:pithosContainer];
391
        }
392
        [self resetLocalStateWithAll:NO];
393
    }
394
}
395

    
396
- (void)setPithos:(ASIPithos *)aPithos {
397
    if (!pithos) {
398
        pithos = [[ASIPithos pithos] retain];
399
        pithos.authUser = [[aPithos.authUser copy] autorelease];
400
        pithos.authToken = [[aPithos.authToken copy] autorelease];
401
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
402
        pithos.authURL = [[aPithos.authURL copy] autorelease];
403
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
404
    }
405
    if (aPithos && 
406
        (![aPithos.authUser isEqualToString:pithos.authUser] || 
407
         ![aPithos.authToken isEqualToString:pithos.authToken] || 
408
         ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
409
        [self resetDaemon];
410
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
411
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
412
            [self resetLocalStateWithAll:YES];
413
        pithos.authUser = [[aPithos.authUser copy] autorelease];
414
        pithos.authToken = [[aPithos.authToken copy] autorelease];
415
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
416
        pithos.authURL = [[aPithos.authURL copy] autorelease];
417
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
418
    }        
419
}
420

    
421
#pragma mark -
422
#pragma mark Sync
423

    
424
- (void)syncOperationStarted {
425
    @synchronized(self) {
426
        syncOperationCount++;
427
    }
428
}
429

    
430
- (void)syncOperationFinishedWithSuccess:(BOOL)operationSuccessfull {
431
    @synchronized(self) {
432
        if (!operationSuccessfull)
433
            syncIncomplete = YES;
434
        if (syncOperationCount == 0) {
435
            // XXX This shouldn't happen, maybe change operationCount to a BOOL as operationsPending in browser
436
            NSLog(@"Sync::WARNING: tried to decrease syncOperationCount when 0");
437
            return;
438
        }
439
        syncOperationCount--;
440
        if (syncOperationCount == 0) {
441
            if (!syncIncomplete) {
442
                self.lastCompletedSync = [NSDate date];
443
                dispatch_async(dispatch_get_main_queue(), ^{
444
                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
445
                                                          message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync] 
446
                                                    pithosAccount:pithosAccount];
447
                    
448
                });
449
            }
450
            [self emptyTempTrash];
451
            if (newSyncRequested && daemonActive)
452
                [self sync];
453
        }
454
    }
455
}
456

    
457
- (BOOL)isSyncing {
458
    @synchronized(self) {
459
        return ((syncOperationCount > 0) && daemonActive);
460
    }
461
}
462

    
463
- (void)syncLate {
464
    @synchronized(self) {
465
        if ([self isSyncing])
466
            syncLate = YES;
467
    }
468
}
469

    
470
- (void)sync {
471
    @synchronized(self) {
472
        if ([self isSyncing]) {
473
            // If at least one operation is running return
474
            newSyncRequested = YES;
475
            return;
476
        } else if (daemonActive && containersCount) {
477
            // The first operation is the server listing
478
            [self syncOperationStarted];
479
            newSyncRequested = NO;
480
            syncIncomplete = NO;
481
            syncLate = NO;
482
        } else {
483
            return;
484
        }
485
    }
486

    
487
    NSFileManager *fileManager = [NSFileManager defaultManager];
488
    BOOL isDirectory;
489
    NSError *error = nil;
490
    if (![fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]) {
491
        if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
492
            error) {
493
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
494
                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", directoryPath] 
495
                                                      error:error];
496
            [self syncOperationFinishedWithSuccess:NO];
497
            return;
498
        }
499
    } else if (!isDirectory) {
500
        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
501
                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", directoryPath] 
502
                                                  error:nil];
503
        [self syncOperationFinishedWithSuccess:NO];
504
        return;
505
    }
506
    for (ASIPithosContainer *pithosContainer in pithosContainers) {
507
        NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
508
        error = nil;
509
        if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
510
            if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
511
                error) {
512
                [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
513
                                                        message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", 
514
                                                                 containerDirectoryPath] 
515
                                                          error:error];
516
                [self syncOperationFinishedWithSuccess:NO];
517
                return;
518
            }
519
        } else if (!isDirectory) {
520
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
521
                                                    message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", 
522
                                                             containerDirectoryPath] 
523
                                                      error:nil];
524
            [self syncOperationFinishedWithSuccess:NO];
525
            return;
526
        }
527
    }
528
    containersIndex = 0;
529
    self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:containersCount];
530
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
531
                                                                                            containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
532
                                                                                                    limit:0 
533
                                                                                                   marker:nil 
534
                                                                                                   prefix:nil 
535
                                                                                                delimiter:nil 
536
                                                                                                     path:nil 
537
                                                                                                     meta:nil 
538
                                                                                                   shared:NO 
539
                                                                                                    until:nil 
540
                                                                                          ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
541
    containerRequest.delegate = self;
542
    containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
543
    containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
544
    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
545
                                                               message:@"Sync: Getting server listing" 
546
                                                         pithosAccount:pithosAccount];
547
    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
548
                                 activity, @"activity", 
549
                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
550
                                 @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
551
                                 @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
552
                                 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
553
                                 [NSNumber numberWithUnsignedInteger:10], @"retries", 
554
                                 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector", 
555
                                 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector", 
556
                                 nil];
557
    [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
558
}
559

    
560
- (void)emptyTempTrash {
561
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
562
    NSString *trashDirPath = self.tempTrashDirPath;
563
    if (trashDirPath) {
564
        NSFileManager *fileManager = [NSFileManager defaultManager];
565
        NSError *error = nil;
566
//        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
567
        NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
568
        if (error) {
569
            dispatch_async(dispatch_get_main_queue(), ^{
570
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
571
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", trashDirPath] 
572
                                                          error:error];
573
            });
574
            [pool drain];
575
            return;
576
        }
577
        if ([subPaths count]) {
578
//            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
579
//            for (NSString *subPath in subPaths) {
580
//                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
581
//            }
582
//            [self syncOperationStarted];
583
//            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
584
//                if (error) {
585
//                    dispatch_async(dispatch_get_main_queue(), ^{
586
//                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
587
//                                                                message:@"Cannot move files to Trash" 
588
//                                                                  error:error];
589
//                    });
590
//                }
591
//                [self syncOperationFinishedWithSuccess:YES];
592
//            }];
593
            for (NSString *subPath in subPaths) {
594
                NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
595
                error = nil;
596
                if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
597
                    dispatch_async(dispatch_get_main_queue(), ^{
598
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
599
                                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
600
                                                                  error:error];
601
                    });
602
            }
603
        }
604
    }
605
    [pool drain];
606
}
607

    
608
- (BOOL)moveToTempTrashFile:(NSString *)filePath pithosContainer:(ASIPithosContainer *)pithosContainer {
609
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
610
    if (!self.tempTrashDirPath) {
611
        [pool drain];
612
        return NO;
613
    }
614
    NSFileManager *fileManager = [NSFileManager defaultManager];
615
    BOOL isDirectory;
616
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
617
    NSError *error = nil;
618
    NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
619
    NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath withString:self.tempTrashDirPath];
620
    NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
621
    if (fileExists && isDirectory) {
622
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
623
        if (error) {
624
            dispatch_async(dispatch_get_main_queue(), ^{
625
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
626
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", filePath] 
627
                                                          error:error];
628
            });
629
            [pool drain];
630
            return NO;
631
        }
632
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
633
            dispatch_async(dispatch_get_main_queue(), ^{
634
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
635
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
636
                                                          error:error];
637
            });
638
            [pool drain];
639
            return NO;
640
        }
641
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
642
            dispatch_async(dispatch_get_main_queue(), ^{
643
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
644
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
645
                                                                 filePath, newFilePath] 
646
                                                          error:error];
647
            });
648
            [pool drain];
649
            return NO;
650
        }
651
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
652
        if (currentState) {
653
            currentState.filePath = newFilePath;
654
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
655
            [currentLocalObjectStates removeObjectForKey:filePath];        
656
        } else {
657
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
658
                                                                                       blockHash:pithosContainer.blockHash 
659
                                                                                       blockSize:pithosContainer.blockSize] 
660
                                         forKey:newFilePath];
661
        }
662
        for (NSString *subPath in subPaths) {
663
            NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
664
            NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
665
                                                                              withString:self.tempTrashDirPath];
666
            currentState = [currentLocalObjectStates objectForKey:subFilePath];
667
            if (currentState) {
668
                currentState.filePath = newSubFilePath;
669
                [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
670
                [currentLocalObjectStates removeObjectForKey:subFilePath];
671
            } else {
672
                [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
673
                                                                                           blockHash:pithosContainer.blockHash 
674
                                                                                           blockSize:pithosContainer.blockSize] 
675
                                             forKey:newSubFilePath];
676
            }        
677
        }
678
    } else if (fileExists && !isDirectory) {
679
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
680
            dispatch_async(dispatch_get_main_queue(), ^{
681
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
682
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
683
                                                          error:error];
684
            });
685
            [pool drain];
686
            return NO;
687
        }
688
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
689
            dispatch_async(dispatch_get_main_queue(), ^{
690
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
691
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
692
                                                                 filePath, newFilePath] 
693
                                                          error:error];
694
            });
695
            [pool drain];
696
            return NO;
697
        }
698
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
699
        if (currentState) {
700
            currentState.filePath = newFilePath;
701
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
702
            [currentLocalObjectStates removeObjectForKey:filePath];        
703
        } else {
704
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
705
                                                                                       blockHash:pithosContainer.blockHash 
706
                                                                                       blockSize:pithosContainer.blockSize] 
707
                                         forKey:newFilePath];
708
        }
709
    }
710
    [pool drain];
711
    return YES;
712
}
713

    
714
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
715
    if ([hash length] != 64)
716
        return NO;
717
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
718
    PithosLocalObjectState *localState;
719
    NSFileManager *fileManager = [NSFileManager defaultManager];
720
    BOOL isDirectory;
721
    NSError *error = nil;
722
    for (NSString *localFilePath in currentLocalObjectStates) {
723
        localState = [currentLocalObjectStates objectForKey:localFilePath];
724
        if (!localState.isDirectory && [hash isEqualToString:localState.hash] && 
725
            [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
726
            if ([localFilePath hasPrefix:directoryPath]) {
727
                if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
728
                    dispatch_async(dispatch_get_main_queue(), ^{
729
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
730
                                                                message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
731
                                                                         localFilePath, filePath] 
732
                                                                  error:error];
733
                    });
734
                } else {
735
                    [pool drain];
736
                    return YES;
737
                }
738
            } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
739
                if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
740
                    dispatch_async(dispatch_get_main_queue(), ^{
741
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
742
                                                                message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
743
                                                                         localFilePath, filePath] 
744
                                                                  error:error];
745
                    });
746
                } else {
747
                    localState.filePath = filePath;
748
                    [currentLocalObjectStates setObject:localState forKey:filePath];
749
                    [currentLocalObjectStates removeObjectForKey:localFilePath];
750
                    [pool drain];
751
                    return YES;
752
                }
753
            }
754
        }
755
    }
756
    [pool drain];
757
    return NO;
758
}
759

    
760
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
761
                     localFilePath:(NSString *)filePath 
762
                   pithosContainer:(ASIPithosContainer *)pithosContainer {
763
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
764
    NSFileManager *fileManager = [NSFileManager defaultManager];
765
    NSError *error;
766
    BOOL isDirectory;
767
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
768
    NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
769
    NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
770
    PithosLocalObjectState *storedState = [containerStoredLocalObjectStates objectForKey:object.name];
771
    // Remote updated info
772
    NSError *remoteError;
773
    BOOL remoteIsDirectory;
774
    BOOL remoteObjectExists = [PithosUtilities objectExistsAtPithos:pithos 
775
                                                      containerName:pithosContainer.name 
776
                                                         objectName:object.name 
777
                                                              error:&remoteError 
778
                                                        isDirectory:&remoteIsDirectory 
779
                                                     sharingAccount:nil];
780
    if (!object || !object.objectHash) {
781
        // Delete local object
782
        if (remoteObjectExists) {
783
            // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
784
            syncIncomplete = YES;
785
        }
786
        NSLog(@"Sync::delete local object: %@", filePath);
787
        if (!fileExists || [self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
788
            dispatch_async(dispatch_get_main_queue(), ^{
789
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
790
                                                      message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@' locally (finished)", 
791
                                                               pithosContainer.name, object.name] 
792
                                                pithosAccount:pithosAccount];
793
            });
794
            [containerStoredLocalObjectStates removeObjectForKey:object.name];
795
            [self saveLocalState];
796
        }
797
    } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
798
        // Create local directory object
799
        if (!remoteObjectExists || !remoteIsDirectory) {
800
            // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
801
            syncIncomplete = YES;
802
            [pool drain];
803
            return;
804
        }
805
        NSLog(@"Sync::create local directory object: %@", filePath);
806
        BOOL directoryCreated = NO;
807
        if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
808
            NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
809
            error = nil;
810
            if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
811
                dispatch_async(dispatch_get_main_queue(), ^{
812
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
813
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
814
                                                              error:error];
815
                });
816
            } else {
817
                directoryCreated = YES;
818
                storedState.filePath = filePath;
819
                storedState.isDirectory = YES;
820
                [self saveLocalState];
821
            }
822
        } else {
823
            NSLog(@"Sync::local directory object exists: %@", filePath);
824
            directoryCreated = YES;
825
        }
826
        if (directoryCreated)
827
            dispatch_async(dispatch_get_main_queue(), ^{
828
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
829
                                                      message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@' locally (finished)", 
830
                                                               pithosContainer.name, object.name] 
831
                                                pithosAccount:pithosAccount];
832
            });
833
    } else if (object.bytes == 0) {
834
        // Create local object with zero length
835
        if (!remoteObjectExists || remoteIsDirectory) {
836
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
837
            syncIncomplete = YES;
838
            [pool drain];
839
            return;
840
        }
841
        NSLog(@"Sync::create local zero length object: %@", filePath);
842
        BOOL fileCreated = NO;
843
        if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath pithosContainer:pithosContainer])) {
844
            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
845
            // Create directory of the file, if it doesn't exist
846
            error = nil;
847
            if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
848
                dispatch_async(dispatch_get_main_queue(), ^{
849
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
850
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
851
                                                              error:error];
852
                });
853
            }
854
            error = nil;
855
            if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
856
                dispatch_async(dispatch_get_main_queue(), ^{
857
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
858
                                                            message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
859
                                                              error:error];
860
                });
861
            } else {
862
                fileCreated = YES;
863
                storedState.filePath = filePath;
864
                storedState.hash = object.objectHash;
865
                storedState.tmpFilePath = nil;
866
                [self saveLocalState];
867
            }
868
        } else {
869
            NSLog(@"Sync::local zero length object exists: %@", filePath);
870
            fileCreated = YES;
871
        }
872
        if (fileCreated)
873
            dispatch_async(dispatch_get_main_queue(), ^{
874
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
875
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
876
                                                               pithosContainer.name, object.name] 
877
                                                pithosAccount:pithosAccount];
878
            });
879
    } else if (storedState.tmpFilePath == nil) {
880
        // Create new local object
881
        if (!remoteObjectExists || remoteIsDirectory) {
882
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
883
            syncIncomplete = YES;
884
            [pool drain];
885
            return;
886
        }
887
        // Create directory of the file, if it doesn't exist
888
        error = nil;
889
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
890
            dispatch_async(dispatch_get_main_queue(), ^{
891
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
892
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
893
                                                          error:error];
894
            });
895
        }
896
        // Check first if a local copy exists
897
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
898
            storedState.filePath = filePath;
899
            storedState.hash = object.objectHash;
900
            [self saveLocalState];
901
            dispatch_async(dispatch_get_main_queue(), ^{
902
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
903
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
904
                                                               pithosContainer.name, object.name] 
905
                                                pithosAccount:pithosAccount];
906
            });
907
        } else {
908
            [self syncOperationStarted];
909
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
910
                                                                                                containerName:pithosContainer.name 
911
                                                                                                       object:object 
912
                                                                                                   blockIndex:0 
913
                                                                                                    blockSize:pithosContainer.blockSize];
914
            objectRequest.delegate = self;
915
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
916
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
917
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
918
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", 
919
                                                                                pithosContainer.name, object.name] 
920
                                                                    totalBytes:object.bytes 
921
                                                                  currentBytes:0 
922
                                                                 pithosAccount:pithosAccount];
923
            dispatch_async(dispatch_get_main_queue(), ^{
924
                [activityFacility updateActivity:activity withMessage:activity.message];
925
            });
926
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
927
                                      pithosContainer, @"pithosContainer", 
928
                                      object, @"pithosObject", 
929
                                      [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(pithosContainer.blockSize + 0.0)))], @"missingBlocks", 
930
                                      [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
931
                                      filePath, @"filePath", 
932
                                      activity, @"activity", 
933
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
934
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
935
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage", 
936
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
937
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
938
                                      NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector", 
939
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
940
                                      nil];
941
            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
942
                [activityFacility updateActivity:activity 
943
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
944
                                                  objectRequest.containerName, 
945
                                                  [[objectRequest.userInfo objectForKey:@"pithosObject"] name], 
946
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
947
                                      totalBytes:activity.totalBytes 
948
                                    currentBytes:(activity.currentBytes + size)];
949
            }];
950
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
951
        }
952
    } else {
953
        // Resume local object download
954
        if (!remoteObjectExists || remoteIsDirectory) {
955
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
956
            syncIncomplete = YES;
957
            [pool drain];
958
            return;
959
        }
960
        // Create directory of the file, if it doesn't exist
961
        error = nil;
962
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
963
            dispatch_async(dispatch_get_main_queue(), ^{
964
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
965
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
966
                                                          error:error];
967
            });
968
        }
969
        // Check first if a local copy exists
970
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
971
            storedState.filePath = filePath;
972
            storedState.hash = object.objectHash;
973
            // Delete incomplete temp download
974
            storedState.tmpFilePath = nil;
975
            [self saveLocalState];
976
            dispatch_async(dispatch_get_main_queue(), ^{
977
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
978
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", 
979
                                                               pithosContainer.name, object.name] 
980
                                                pithosAccount:pithosAccount];
981
            });
982
        } else {
983
            [self syncOperationStarted];
984
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithPithos:pithos 
985
                                                                                             containerName:pithosContainer.name 
986
                                                                                                objectName:object.name];
987
            objectRequest.delegate = self;
988
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
989
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
990
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
991
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (0%%)", 
992
                                                                                pithosContainer.name, object.name] 
993
                                                                    totalBytes:object.bytes 
994
                                                                  currentBytes:0 
995
                                                                 pithosAccount:pithosAccount];
996
            dispatch_async(dispatch_get_main_queue(), ^{
997
                [activityFacility updateActivity:activity withMessage:activity.message];
998
            });
999
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1000
                                      pithosContainer, @"pithosContainer", 
1001
                                      object, @"pithosObject", 
1002
                                      filePath, @"filePath", 
1003
                                      activity, @"activity", 
1004
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
1005
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
1006
                                      [NSString stringWithFormat:@"Sync: Downloading '%@/%@' (100%%)", pithosContainer.name, object.name], @"finishedActivityMessage", 
1007
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1008
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1009
                                      NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector", 
1010
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1011
                                      nil];
1012
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1013
        }
1014
    }
1015
    [pool drain];
1016
}
1017

    
1018
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
1019
                                  object:(ASIPithosObject *)object 
1020
                           localFilePath:(NSString *)filePath 
1021
                         pithosContainer:(ASIPithosContainer *)pithosContainer {
1022
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1023
    [self syncOperationStarted];
1024
    NSFileManager *fileManager = [NSFileManager defaultManager];
1025
    BOOL isDirectory;
1026
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1027
    if (currentState.isDirectory) {
1028
        // Create remote directory object
1029
        if (!fileExists || !isDirectory) {
1030
            // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
1031
            [self syncOperationFinishedWithSuccess:NO];
1032
            [pool drain];
1033
            return;
1034
        }
1035
        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos 
1036
                                                                                           containerName:pithosContainer.name 
1037
                                                                                              objectName:object.name 
1038
                                                                                                    eTag:nil 
1039
                                                                                             contentType:@"application/directory" 
1040
                                                                                         contentEncoding:nil 
1041
                                                                                      contentDisposition:nil 
1042
                                                                                                manifest:nil 
1043
                                                                                                 sharing:nil 
1044
                                                                                                isPublic:ASIPithosObjectRequestPublicIgnore 
1045
                                                                                                metadata:nil 
1046
                                                                                                    data:[NSData data]];
1047
        objectRequest.delegate = self;
1048
        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1049
        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1050
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
1051
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@/%@'", 
1052
                                                                            pithosContainer.name, object.name] 
1053
                                                             pithosAccount:pithosAccount];
1054
        dispatch_async(dispatch_get_main_queue(), ^{
1055
            [activityFacility updateActivity:activity withMessage:activity.message];
1056
        });
1057
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1058
                                  pithosContainer, @"pithosContainer", 
1059
                                  object, @"pithosObject", 
1060
                                  activity, @"activity", 
1061
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
1062
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
1063
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
1064
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1065
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
1066
                                  NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
1067
                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1068
                                  nil];
1069
        [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1070
    } else if (![currentState exists]) {
1071
        // Delete remote object
1072
        if (fileExists) {
1073
            // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
1074
            syncIncomplete = YES;
1075
        }
1076
        if ([pithosContainer.name isEqualToString:@"trash"]) {
1077
            // Delete
1078
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
1079
                                                                                            containerName:pithosContainer.name 
1080
                                                                                               objectName:object.name];
1081
            objectRequest.delegate = self;
1082
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1083
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1084
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1085
                                                                       message:[NSString stringWithFormat:@"Sync: Deleting '%@/%@'", 
1086
                                                                                pithosContainer.name, object.name] 
1087
                                                                 pithosAccount:pithosAccount];
1088
            dispatch_async(dispatch_get_main_queue(), ^{
1089
                [activityFacility updateActivity:activity withMessage:activity.message];
1090
            });
1091
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1092
                                      pithosContainer, @"pithosContainer", 
1093
                                      object, @"pithosObject", 
1094
                                      activity, @"activity", 
1095
                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
1096
                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
1097
                                      [NSString stringWithFormat:@"Sync: Deleting '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
1098
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1099
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
1100
                                      NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector", 
1101
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1102
                                      nil];
1103
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1104
        } else {
1105
            // Move to container trash
1106
            NSString *safeName;
1107
            if ([PithosUtilities isContentTypeDirectory:object.contentType])
1108
                safeName = [PithosUtilities safeSubdirNameForPithos:pithos containerName:@"trash" subdirName:object.name];
1109
            else
1110
                safeName = [PithosUtilities safeObjectNameForPithos:pithos containerName:@"trash" objectName:object.name];
1111
            if (safeName) {
1112
                ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos 
1113
                                                                                       containerName:pithosContainer.name 
1114
                                                                                          objectName:object.name 
1115
                                                                            destinationContainerName:@"trash" 
1116
                                                                               destinationObjectName:safeName 
1117
                                                                                       checkIfExists:NO];
1118
                if (objectRequest) {
1119
                    objectRequest.delegate = self;
1120
                    objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1121
                    objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1122
                    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
1123
                                                                               message:[NSString stringWithFormat:@"Sync: Moving to trash '%@/%@'", 
1124
                                                                                        pithosContainer.name, object.name] 
1125
                                                                         pithosAccount:pithosAccount];
1126
                    dispatch_async(dispatch_get_main_queue(), ^{
1127
                        [activityFacility updateActivity:activity withMessage:activity.message];
1128
                    });
1129
                    objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1130
                                              pithosContainer, @"pithosContainer", 
1131
                                              object, @"pithosObject", 
1132
                                              activity, @"activity", 
1133
                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (stopped)", pithosContainer.name, object.name], @"stoppedActivityMessage", 
1134
                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (failed)", pithosContainer.name, object.name], @"failedActivityMessage", 
1135
                                              [NSString stringWithFormat:@"Sync: Moving to trash '%@/%@' (finished)", pithosContainer.name, object.name], @"finishedActivityMessage", 
1136
                                              [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
1137
                                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1138
                                              NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector", 
1139
                                              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1140
                                              nil];
1141
                    [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1142
                } else {
1143
                    [self syncOperationFinishedWithSuccess:NO];
1144
                }
1145
            } else {
1146
                [self syncOperationFinishedWithSuccess:NO];
1147
            }
1148
        }
1149
    } else {
1150
        // Upload file to remote object
1151
        if (!fileExists || isDirectory) {
1152
            // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
1153
            [self syncOperationFinishedWithSuccess:NO];
1154
            [pool drain];
1155
            return;
1156
        }
1157
        NSError *error = nil;
1158
        object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1159
        if (object.contentType == nil)
1160
            object.contentType = @"application/octet-stream";
1161
        if (error)
1162
            NSLog(@"contentType detection error: %@", error);
1163
        NSArray *hashes = nil;
1164
        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
1165
                                                                                    containerName:pithosContainer.name 
1166
                                                                                       objectName:object.name 
1167
                                                                                      contentType:object.contentType 
1168
                                                                                        blockSize:pithosContainer.blockSize 
1169
                                                                                        blockHash:pithosContainer.blockHash 
1170
                                                                                          forFile:filePath 
1171
                                                                                    checkIfExists:NO 
1172
                                                                                           hashes:&hashes 
1173
                                                                                   sharingAccount:nil];
1174
        if (objectRequest) {
1175
            objectRequest.delegate = self;
1176
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1177
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1178
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
1179
                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (0%%)", 
1180
                                                                                pithosContainer.name, object.name]
1181
                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1182
                                                                  currentBytes:0 
1183
                                                                 pithosAccount:pithosAccount];
1184
            dispatch_async(dispatch_get_main_queue(), ^{
1185
                [activityFacility updateActivity:activity withMessage:activity.message];
1186
            });
1187
            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1188
             [NSDictionary dictionaryWithObjectsAndKeys:
1189
              pithosContainer, @"pithosContainer", 
1190
              object, @"pithosObject", 
1191
              filePath, @"filePath", 
1192
              hashes, @"hashes", 
1193
              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
1194
              activity, @"activity", 
1195
              [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
1196
              [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
1197
              [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
1198
              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
1199
              [NSNumber numberWithUnsignedInteger:10], @"retries", 
1200
              NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
1201
              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
1202
              nil]];
1203
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
1204
        } else {
1205
            [self syncOperationFinishedWithSuccess:NO];
1206
        }
1207
    }
1208
    [pool drain];
1209
}
1210

    
1211
#pragma mark -
1212
#pragma mark ASIHTTPRequestDelegate
1213

    
1214
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1215
    // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1216
    NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1217
                                                                             selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) 
1218
                                                                               object:request] autorelease];
1219
    operation.completionBlock = ^{
1220
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1221
        if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1222
            dispatch_async(dispatch_get_main_queue(), ^{
1223
                [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1224
                                  withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1225
            });
1226
            [self syncOperationFinishedWithSuccess:NO];
1227
        }
1228
        [pool drain];
1229
    };
1230
    [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1231
    [callbackQueue addOperation:operation];
1232
}
1233

    
1234
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1235
    if (request.isCancelled) {
1236
        // Request has been cancelled 
1237
        // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1238
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1239
                   withObject:request];
1240
    } else {
1241
        // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1242
        NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self 
1243
                                                                                 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) 
1244
                                                                                   object:request] autorelease];
1245
        operation.completionBlock = ^{
1246
            NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1247
            if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1248
                dispatch_async(dispatch_get_main_queue(), ^{
1249
                    [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1250
                                      withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1251
                });
1252
                [self syncOperationFinishedWithSuccess:NO];
1253
            }
1254
            [pool drain];
1255
        };
1256
        [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1257
        [callbackQueue addOperation:operation];
1258
    }
1259
}
1260

    
1261
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
1262
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1263
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1264
    NSLog(@"Sync::list request finished: %@", containerRequest.url);
1265
    if (operation.isCancelled) {
1266
        [self listRequestFailed:containerRequest];
1267
    } else if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
1268
        if (containerRequest.responseStatusCode == 200) {
1269
            NSArray *someObjects = [containerRequest objects];
1270
            if (objects == nil) {
1271
                objects = [[NSMutableArray alloc] initWithArray:someObjects];
1272
            } else {
1273
                [objects addObjectsFromArray:someObjects];
1274
            }
1275
            if ([someObjects count] < 10000) {
1276
                ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
1277
                pithosContainer.blockHash = [containerRequest blockHash];
1278
                pithosContainer.blockSize = [containerRequest blockSize];
1279
                pithosContainer.lastModified = [containerRequest lastModified];
1280
                NSMutableDictionary *containerRemoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
1281
                for (ASIPithosObject *object in objects) {
1282
                    [containerRemoteObjects setObject:object forKey:object.name];
1283
                }
1284
                [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
1285
                [objects release];
1286
                objects = nil;
1287
            } else {
1288
                // Do an additional request to fetch more objects
1289
                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1290
                                                                                                           containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
1291
                                                                                                                   limit:0 
1292
                                                                                                                  marker:[[someObjects lastObject] name] 
1293
                                                                                                                  prefix:nil 
1294
                                                                                                               delimiter:nil 
1295
                                                                                                                    path:nil 
1296
                                                                                                                    meta:nil 
1297
                                                                                                                  shared:NO 
1298
                                                                                                                   until:nil 
1299
                                                                                                         ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
1300
                newContainerRequest.delegate = self;
1301
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1302
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1303
                newContainerRequest.userInfo = containerRequest.userInfo;
1304
                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1305
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1306
                [pool drain];
1307
                return;
1308
            }
1309
        } else {
1310
            ASIPithosContainer *pithosContainer = [pithosContainers objectAtIndex:containersIndex];
1311
            NSMutableDictionary *containerRemoteObjects = [previousRemoteObjects objectForKey:pithosContainer.name];
1312
            if (containerRemoteObjects) 
1313
                [remoteObjects setObject:containerRemoteObjects forKey:pithosContainer.name];
1314
        }
1315
        containersIndex++;
1316
        if (containersIndex < containersCount) {
1317
            // Do a request for the next container
1318
            ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
1319
                                                                                                       containerName:[[pithosContainers objectAtIndex:containersIndex] name] 
1320
                                                                                                               limit:0 
1321
                                                                                                              marker:nil 
1322
                                                                                                              prefix:nil 
1323
                                                                                                           delimiter:nil 
1324
                                                                                                                path:nil 
1325
                                                                                                                meta:nil 
1326
                                                                                                              shared:NO 
1327
                                                                                                               until:nil 
1328
                                                                                                     ifModifiedSince:[[pithosContainers objectAtIndex:containersIndex] lastModified]];
1329
            newContainerRequest.delegate = self;
1330
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1331
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1332
            newContainerRequest.userInfo = containerRequest.userInfo;
1333
            [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1334
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1335
            [pool drain];
1336
            return;
1337
        }
1338
        self.previousRemoteObjects = remoteObjects;
1339
        // remoteObjects contains all remote objects for the legal containers, without enforcing directory exclusions
1340
        
1341
        if (operation.isCancelled) {
1342
            [self listRequestFailed:containerRequest];
1343
            [pool drain];
1344
            return;
1345
        }
1346

    
1347
        dispatch_async(dispatch_get_main_queue(), ^{
1348
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1349
                              withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1350
        });
1351
        NSFileManager *fileManager = [NSFileManager defaultManager];
1352
        
1353
        // Compute current state of legal existing local objects 
1354
        // and add an empty stored state for legal new local objects since last sync
1355
        self.currentLocalObjectStates = [NSMutableDictionary dictionary];
1356
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1357
            NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1358
            NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1359
            BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1360
            NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1361
            NSDirectoryEnumerator *dirEnumerator = [fileManager enumeratorAtPath:containerDirectoryPath];
1362
            for (NSString *objectName in dirEnumerator) {
1363
                if (operation.isCancelled) {
1364
                    operation.completionBlock = nil;
1365
                    [self saveLocalState];
1366
                    [self syncOperationFinishedWithSuccess:NO];
1367
                    [pool drain];
1368
                    return;
1369
                }
1370

    
1371
                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1372
                NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
1373
                BOOL isDirectory;
1374
                BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
1375
                if ([[attributes fileType] isEqualToString:NSFileTypeSymbolicLink]) {
1376
                    dispatch_async(dispatch_get_main_queue(), ^{
1377
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Sync Error" 
1378
                                                                message:[NSString stringWithFormat:@"Sync directory at '%@' contains symbolic links. Remove them and re-activate sync for account '%@'.", 
1379
                                                                         containerDirectoryPath, pithosAccount.name] 
1380
                                                                  error:nil];
1381
                    });
1382
                    pithosAccount.syncActive = NO;
1383
                    return;
1384
                } else if (fileExists) {
1385
                    NSArray *pathComponents = [objectName pathComponents];
1386
                    if ([pathComponents count] == 1) {
1387
                        if ([containerExcludedDirectories containsObject:[objectName lowercaseString]]) {
1388
                            // Skip excluded directory and its descendants, or root file with same name
1389
                            if (isDirectory)
1390
                                [dirEnumerator skipDescendants];
1391
                            // Remove stored state if any
1392
                            [containerStoredLocalObjectStates removeObjectForKey:objectName];
1393
                            continue;
1394
                        } else if (!isDirectory && containerExludeRootFiles) {
1395
                            // Skip excluded root file
1396
                            // Remove stored state if any
1397
                            [containerStoredLocalObjectStates removeObjectForKey:objectName];
1398
                            continue;
1399
                        }
1400
                    }
1401
                    // Include local object
1402
                    PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1403
                    if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1404
                        // New or modified existing local object, compute current state
1405
                        if (!storedLocalObjectState)
1406
                            // For new local object, also create empty stored state
1407
                            [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1408
                        [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
1409
                                                                                                   blockHash:pithosContainer.blockHash 
1410
                                                                                                   blockSize:pithosContainer.blockSize] 
1411
                                                     forKey:filePath];
1412
                    } else {
1413
                        // Local object hasn't changed, set stored state also to current
1414
                        [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1415
                    }
1416
                }
1417
            }
1418
            [self saveLocalState];
1419
        }    
1420
        
1421
        if (operation.isCancelled) {
1422
            operation.completionBlock = nil;
1423
            [self syncOperationFinishedWithSuccess:NO];
1424
            [pool drain];
1425
            return;
1426
        }
1427

    
1428
        // Add an empty stored state for legal new remote objects since last sync
1429
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1430
            NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1431
            BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1432
            NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1433
            NSMutableDictionary *containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
1434
            for (NSString *objectName in containerRemoteObjects) {
1435
                if (operation.isCancelled) {
1436
                    operation.completionBlock = nil;
1437
                    [self saveLocalState];
1438
                    [self syncOperationFinishedWithSuccess:NO];
1439
                    [pool drain];
1440
                    return;
1441
                }
1442

    
1443
                ASIPithosObject *object = [containerRemoteObjects objectForKey:objectName];
1444
                NSString *localObjectName;
1445
                if ([object.name hasSuffix:@"/"])
1446
                    localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1447
                else
1448
                    localObjectName = [NSString stringWithString:object.name];
1449
                NSArray *pathComponents = [localObjectName pathComponents];
1450
                if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1451
                    // Skip excluded directory object and its descendants, or root file object with same name
1452
                    // Remove stored state if any
1453
                    [containerStoredLocalObjectStates removeObjectForKey:object.name];
1454
                    continue;
1455
                } else if (containerExludeRootFiles && 
1456
                           ([pathComponents count] == 1) && 
1457
                           ![PithosUtilities isContentTypeDirectory:object.contentType]) {
1458
                    // Skip root file object
1459
                    // Remove stored state if any
1460
                    [containerStoredLocalObjectStates removeObjectForKey:object.name];                    
1461
                    continue;
1462
                }
1463
                if (![containerStoredLocalObjectStates objectForKey:object.name])
1464
                    [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:object.name];
1465
            }
1466
            [self saveLocalState];
1467
        }
1468

    
1469
        if (operation.isCancelled) {
1470
            operation.completionBlock = nil;
1471
            [self syncOperationFinishedWithSuccess:NO];
1472
            [pool drain];
1473
            return;
1474
        }
1475

    
1476
        // For each stored state compare with current and remote state
1477
        // Stored states of local objects that have been deleted, 
1478
        // haven't been checked for legality (only existing local remote objects)
1479
        // These should be identified and skipped
1480
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1481
            NSString *containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1482
            NSArray *containerExcludedDirectories = [containersDictionary objectForKey:pithosContainer.name];
1483
            BOOL containerExludeRootFiles = [containerExcludedDirectories containsObject:@""];
1484
            NSMutableDictionary *containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1485
            NSMutableDictionary *containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
1486
            for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1487
                if (operation.isCancelled) {
1488
                    operation.completionBlock = nil;
1489
                    [self syncOperationFinishedWithSuccess:NO];
1490
                    [pool drain];
1491
                    return;
1492
                }
1493

    
1494
                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1495
                if ([objectName hasSuffix:@"/"])
1496
                    filePath = [filePath stringByAppendingString:@":"];
1497
                ASIPithosObject *object = [ASIPithosObject object];
1498
                object.name = objectName;
1499
                NSLog(@"Sync::object name: %@", object.name);
1500
            
1501
                PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1502
                PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1503
                if (!currentLocalObjectState) {
1504
                    // The stored state corresponds to a remote or deleted local object, that's why there is no current state
1505
                    // In the latter case it must be checked for legality, which can be determined by its stored state
1506
                    // If it existed locally, but was deleted, state.exists is true, 
1507
                    // else if the stored state is an empty state that was created due to the server object, state.exists is false
1508
                    if (storedLocalObjectState.exists) {
1509
                        NSString *localObjectName;
1510
                        if ([object.name hasSuffix:@"/"])
1511
                            localObjectName = [NSString stringWithFormat:@"%@:", [object.name substringToIndex:([object.name length] -1)]];
1512
                        else
1513
                            localObjectName = [NSString stringWithString:object.name];
1514
                        NSArray *pathComponents = [localObjectName pathComponents];
1515
                        if ([containerExcludedDirectories containsObject:[[pathComponents objectAtIndex:0] lowercaseString]]) {
1516
                            // Skip excluded directory object and its descendants, or root file object with same name
1517
                            // Remove stored state
1518
                            [containerStoredLocalObjectStates removeObjectForKey:object.name];
1519
                            [self saveLocalState];
1520
                            continue;
1521
                        } else if (containerExludeRootFiles && ([pathComponents count] == 1) && !storedLocalObjectState.isDirectory) {
1522
                            // Skip root file object
1523
                            // Remove stored state
1524
                            [containerStoredLocalObjectStates removeObjectForKey:object.name];                    
1525
                            [self saveLocalState];
1526
                            continue;
1527
                        }
1528
                    }
1529
                    // There is also the off case that a local object has been created in the meantime
1530
                    // This call works in any case, existent or non-existent local object 
1531
                    currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
1532
                                                                                     blockHash:pithosContainer.blockHash 
1533
                                                                                     blockSize:pithosContainer.blockSize];
1534
                }
1535
            
1536
                PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1537
                ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1538
                if (remoteObject) {
1539
                    if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1540
                        remoteObjectState.isDirectory = YES;
1541
                    } else {
1542
                        remoteObjectState.hash = remoteObject.objectHash;
1543
                    }
1544
                }
1545

    
1546
                BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1547
                BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1548
                NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1549
                if (!localStateHasChanged) {
1550
                    // Local state hasn't changed
1551
                    if (serverStateHasChanged) {
1552
                        // Server state has changed
1553
                        // Update local state to match that of the server 
1554
                        object.bytes = remoteObject.bytes;
1555
                        object.version = remoteObject.version;
1556
                        object.contentType = remoteObject.contentType;
1557
                        object.objectHash = remoteObject.objectHash;
1558
                        [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1559
                    } else if (!remoteObject && ![currentLocalObjectState exists]) {
1560
                        // Server state hasn't changed
1561
                        // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1562
                        [containerStoredLocalObjectStates removeObjectForKey:objectName];
1563
                        [self saveLocalState];
1564
                    }
1565
                } else {
1566
                    // Local state has changed
1567
                    if (!serverStateHasChanged) {
1568
                        // Server state hasn't changed
1569
                        if (currentLocalObjectState.isDirectory)
1570
                            object.contentType = @"application/directory";
1571
                        else
1572
                            object.objectHash = currentLocalObjectState.hash;
1573
                        [self updateServerStateWithCurrentState:currentLocalObjectState 
1574
                                                         object:object 
1575
                                                  localFilePath:filePath 
1576
                                                pithosContainer:pithosContainer];
1577
                    } else {
1578
                        // Server state has also changed
1579
                        if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1580
                            // Both did the same change (directory)
1581
                            storedLocalObjectState.filePath = filePath;
1582
                            storedLocalObjectState.isDirectory = YES;
1583
                            [self saveLocalState];
1584
                        } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1585
                            // Both did the same change (object edit or delete)
1586
                            if (![remoteObjectState exists]) {
1587
                                [containerStoredLocalObjectStates removeObjectForKey:object.name];
1588
                            } else {
1589
                                storedLocalObjectState.filePath = filePath;
1590
                                storedLocalObjectState.hash = remoteObjectState.hash;
1591
                            }
1592
                            [self saveLocalState];
1593
                        } else {
1594
                            // Conflict, we ask the user which change to keep
1595
                            NSString *informativeText;
1596
                            NSString *firstButtonText;
1597
                            NSString *secondButtonText;
1598
                            
1599
                            if (![remoteObjectState exists]) {
1600
                                // Remote object has been deleted
1601
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.", 
1602
                                                   pithosContainer.name, object.name ];
1603
                                firstButtonText = @"Delete local file";
1604
                                secondButtonText = @"Upload file to server";
1605
                            } else if (![currentLocalObjectState exists]) {
1606
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.", 
1607
                                                   pithosContainer.name, object.name];
1608
                                firstButtonText = @"Download file from server";
1609
                                secondButtonText = @"Delete file on server";
1610
                            } else {
1611
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.", 
1612
                                                   pithosContainer.name, object.name];
1613
                                firstButtonText = @"Keep server version";
1614
                                secondButtonText = @"Keep local version";
1615
                            }
1616
                            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1617
                            [alert setMessageText:@"Conflict"];
1618
                            [alert setInformativeText:informativeText];
1619
                            [alert addButtonWithTitle:firstButtonText];
1620
                            [alert addButtonWithTitle:secondButtonText];
1621
                            [alert addButtonWithTitle:@"Do nothing"];
1622
                            NSInteger choice = [alert runModal];
1623
                            if (choice == NSAlertFirstButtonReturn) {
1624
                                object.bytes = remoteObject.bytes;
1625
                                object.version = remoteObject.version;
1626
                                object.contentType = remoteObject.contentType;
1627
                                object.objectHash = remoteObject.objectHash;
1628
                                [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1629
                            } if (choice == NSAlertSecondButtonReturn) {
1630
                                if (currentLocalObjectState.isDirectory)
1631
                                    object.contentType = @"application/directory";
1632
                                else
1633
                                    object.objectHash = currentLocalObjectState.hash;
1634
                                [self updateServerStateWithCurrentState:currentLocalObjectState 
1635
                                                                 object:object 
1636
                                                          localFilePath:filePath 
1637
                                                        pithosContainer:pithosContainer];
1638
                            }
1639
                        }
1640
                    }
1641
                }
1642
            }
1643
        }
1644
        [self syncOperationFinishedWithSuccess:YES];
1645
    } else {
1646
        [self listRequestFailed:containerRequest];
1647
    }
1648
    [pool drain];
1649
}
1650

    
1651
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1652
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1653
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1654
    if (operation.isCancelled) {
1655
        [objects release];
1656
        objects = nil;
1657
        [pool drain];
1658
        return;        
1659
    }
1660
    if (containerRequest.isCancelled) {
1661
        dispatch_async(dispatch_get_main_queue(), ^{
1662
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1663
                              withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1664
        });
1665
        [objects release];
1666
        objects = nil;
1667
        [self syncOperationFinishedWithSuccess:NO];
1668
        [pool drain];
1669
        return;
1670
    }
1671
    // If the server listing fails, the sync should start over, so just retrying is enough
1672
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1673
    if (retries > 0) {
1674
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1675
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1676
        [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1677
    } else {
1678
        dispatch_async(dispatch_get_main_queue(), ^{
1679
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1680
                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1681
        });
1682
        [objects release];
1683
        objects = nil;
1684
        // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1685
        [self syncOperationFinishedWithSuccess:NO];
1686
    }
1687
    [pool drain];
1688
}
1689

    
1690
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1691
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1692
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1693
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1694
    if (operation.isCancelled) {
1695
        [self requestFailed:objectRequest];
1696
    } else if (objectRequest.responseStatusCode == 206) {
1697
        ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1698
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1699
        NSFileManager *fileManager = [NSFileManager defaultManager];
1700
        NSError *error;
1701
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1702
        
1703
        NSString *downloadsDirPath = self.tempDownloadsDirPath;
1704
        if (!downloadsDirPath) {
1705
            dispatch_async(dispatch_get_main_queue(), ^{
1706
                [activityFacility endActivity:activity 
1707
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1708
            });
1709
            [self syncOperationFinishedWithSuccess:NO];
1710
            [pool drain];
1711
            return;
1712
        }
1713
        
1714
        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1715
        if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1716
            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1717
            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1718
            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1719
            strcpy(tempFileNameCString, tempFileTemplateCString);
1720
            int fileDescriptor = mkstemp(tempFileNameCString);
1721
            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1722
            free(tempFileNameCString);
1723
            if (fileDescriptor == -1) {
1724
                dispatch_async(dispatch_get_main_queue(), ^{
1725
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1726
                                                            message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath] 
1727
                                                              error:nil];
1728
                    [activityFacility endActivity:activity 
1729
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1730
                });
1731
                [self syncOperationFinishedWithSuccess:NO];
1732
                [pool drain];
1733
                return;
1734
            }
1735
            close(fileDescriptor);
1736
            storedState.tmpFilePath = tempFilePath;
1737
            [self saveLocalState];
1738
        }
1739
        
1740

    
1741
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1742
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1743
        [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1744
        [tempFileHandle writeData:[objectRequest responseData]];
1745
        [tempFileHandle closeFile];
1746

    
1747
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1748
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1749
        if (missingBlockIndex == NSNotFound) {
1750
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1751
            NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1752
            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
1753
                dispatch_async(dispatch_get_main_queue(), ^{
1754
                    [activityFacility endActivity:activity 
1755
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1756
                });
1757
                [self syncOperationFinishedWithSuccess:NO];
1758
                [pool drain];
1759
                return;
1760
            } else if (![fileManager fileExistsAtPath:dirPath]) {
1761
                // File doesn't exist but also the containing directory doesn't exist
1762
                // In most cases this should have been resolved as an update of the corresponding local object,
1763
                // but it never hurts to check
1764
                error = nil;
1765
                [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1766
                if (error != nil) {
1767
                    dispatch_async(dispatch_get_main_queue(), ^{
1768
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1769
                                                                message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1770
                                                                  error:error];
1771
                        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1772
                                          withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1773
                    });
1774
                    [self syncOperationFinishedWithSuccess:NO];
1775
                    [pool drain];
1776
                    return;
1777
                }
1778
            }
1779
            // Move file from tmp download
1780
            error = nil;
1781
            [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1782
            if (error != nil) {
1783
                dispatch_async(dispatch_get_main_queue(), ^{
1784
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1785
                                                            message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath] 
1786
                                                              error:error];
1787
                    [activityFacility endActivity:activity 
1788
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1789
                });
1790
                [self syncOperationFinishedWithSuccess:NO];
1791
                [pool drain];
1792
                return;
1793
            }
1794
            dispatch_async(dispatch_get_main_queue(), ^{
1795
                [activityFacility endActivity:activity 
1796
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1797
                                   totalBytes:activity.totalBytes 
1798
                                 currentBytes:activity.totalBytes];
1799
            });
1800

    
1801
            storedState.filePath = filePath;
1802
            storedState.hash = object.objectHash;
1803
            storedState.tmpFilePath = nil;
1804
            [self saveLocalState];
1805
            [self syncOperationFinishedWithSuccess:YES];
1806
            [pool drain];
1807
            return;
1808
        } else {
1809
            if (newSyncRequested || syncLate || operation.isCancelled) {
1810
                [self requestFailed:objectRequest];
1811
            } else {
1812
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1813
                                                                                                       containerName:pithosContainer.name 
1814
                                                                                                              object:object 
1815
                                                                                                          blockIndex:missingBlockIndex 
1816
                                                                                                           blockSize:pithosContainer.blockSize];
1817
                newObjectRequest.delegate = self;
1818
                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1819
                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1820
                newObjectRequest.userInfo = objectRequest.userInfo;
1821
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1822
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1823
                [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1824
                    [activityFacility updateActivity:activity 
1825
                                         withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1826
                                                      newObjectRequest.containerName, object.name, 
1827
                                                      (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1828
                                          totalBytes:activity.totalBytes 
1829
                                        currentBytes:(activity.currentBytes + size)];
1830
                }];
1831
                [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1832
            }
1833
        }
1834
    } else if (objectRequest.responseStatusCode == 412) {
1835
        // The object has changed on the server
1836
        dispatch_async(dispatch_get_main_queue(), ^{
1837
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1838
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1839
        });
1840
        [self syncOperationFinishedWithSuccess:NO];
1841
    } else {
1842
        [self requestFailed:objectRequest];
1843
    }
1844
    [pool drain];
1845
}
1846

    
1847
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1848
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1849
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1850
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1851
    if (operation.isCancelled) {
1852
        [self requestFailed:objectRequest];
1853
    } else if (objectRequest.responseStatusCode == 200) {
1854
        if (newSyncRequested || syncLate || operation.isCancelled) {
1855
            [self requestFailed:objectRequest];
1856
        } else {
1857
            ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1858
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1859
            PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1860
            if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1861
                [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1862
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1863
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath 
1864
                                                                    blockSize:pithosContainer.blockSize 
1865
                                                                    blockHash:pithosContainer.blockHash 
1866
                                                                   withHashes:[objectRequest hashes]];
1867
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1868
            dispatch_async(dispatch_get_main_queue(), ^{
1869
                [activityFacility updateActivity:activity 
1870
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1871
                                                  pithosContainer.name, object.name, 
1872
                                                  (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1873
                                      totalBytes:activity.totalBytes 
1874
                                    currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
1875
            });
1876

    
1877
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1878
                                                                                                   containerName:pithosContainer.name 
1879
                                                                                                          object:object 
1880
                                                                                                      blockIndex:missingBlockIndex 
1881
                                                                                                       blockSize:pithosContainer.blockSize];
1882
            newObjectRequest.delegate = self;
1883
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1884
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1885
            newObjectRequest.userInfo = objectRequest.userInfo;
1886
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1887
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1888
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1889
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1890
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1891
                [activityFacility updateActivity:activity 
1892
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1893
                                                  newObjectRequest.containerName, object.name, 
1894
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1895
                                      totalBytes:activity.totalBytes 
1896
                                    currentBytes:(activity.currentBytes + size)];
1897
            }];
1898
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1899
        }
1900
    } else {
1901
        [self requestFailed:objectRequest];
1902
    }
1903
    [pool drain];
1904
}
1905

    
1906
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1907
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1908
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1909
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1910
    if (operation.isCancelled) {
1911
        [self requestFailed:objectRequest];
1912
    } else if (objectRequest.responseStatusCode == 201) {
1913
        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1914
                                               objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1915
        storedState.isDirectory = YES;
1916
        [self saveLocalState];
1917
        dispatch_async(dispatch_get_main_queue(), ^{
1918
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1919
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1920
        });
1921
        [self syncOperationFinishedWithSuccess:YES];
1922
    } else {
1923
        [self requestFailed:objectRequest];
1924
    }
1925
    [pool drain];
1926
}
1927

    
1928
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1929
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1930
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1931
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1932
    if (operation.isCancelled) {
1933
        [self requestFailed:objectRequest];
1934
    } else if (objectRequest.responseStatusCode == 201) {
1935
        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1936
         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1937
        [self saveLocalState];
1938
        dispatch_async(dispatch_get_main_queue(), ^{
1939
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1940
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1941
        });
1942
        [self syncOperationFinishedWithSuccess:YES];
1943
    } else {
1944
        [self requestFailed:objectRequest];
1945
    }
1946
    [pool drain];
1947
}
1948

    
1949
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1950
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1951
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1952
    NSLog(@"Sync::delete object finished: %@", objectRequest.url);
1953
    if (operation.isCancelled) {
1954
        [self requestFailed:objectRequest];
1955
    } else if (objectRequest.responseStatusCode == 204) {
1956
        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1957
         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1958
        [self saveLocalState];
1959
        dispatch_async(dispatch_get_main_queue(), ^{
1960
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1961
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1962
        });
1963
        [self syncOperationFinishedWithSuccess:YES];
1964
    } else {
1965
        [self requestFailed:objectRequest];
1966
    }
1967
    [pool drain];
1968
}
1969

    
1970
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1971
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1972
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1973
    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1974
    ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1975
    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1976
    PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1977
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1978
    NSUInteger totalBytes = activity.totalBytes;
1979
    NSUInteger currentBytes = activity.currentBytes;
1980
    if (operation.isCancelled) {
1981
        [self requestFailed:objectRequest];
1982
    } else if (objectRequest.responseStatusCode == 201) {
1983
        NSLog(@"Sync::object created: %@", objectRequest.url);
1984
        storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1985
        storedState.hash = object.objectHash;
1986
        [self saveLocalState];
1987
        dispatch_async(dispatch_get_main_queue(), ^{
1988
            [activityFacility endActivity:activity 
1989
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1990
                               totalBytes:totalBytes 
1991
                             currentBytes:totalBytes];
1992
        });
1993
        [self syncOperationFinishedWithSuccess:YES];
1994
    } else if (objectRequest.responseStatusCode == 409) {
1995
        if (newSyncRequested || syncLate || operation.isCancelled) {
1996
            [self requestFailed:objectRequest];
1997
        } else {
1998
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1999
            if (iteration == 0) {
2000
                NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
2001
                dispatch_async(dispatch_get_main_queue(), ^{
2002
                    [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
2003
                });
2004
                [self syncOperationFinishedWithSuccess:NO];
2005
                [pool drain];
2006
                return;
2007
            }
2008
            NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
2009
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
2010
                                                              withMissingHashes:[objectRequest hashes]];
2011
            if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
2012
                currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
2013
            dispatch_async(dispatch_get_main_queue(), ^{
2014
                [activityFacility updateActivity:activity 
2015
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
2016
                                                  pithosContainer.name, object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
2017
                                      totalBytes:totalBytes 
2018
                                    currentBytes:currentBytes];
2019
            });
2020
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
2021
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
2022
                                                                                                             containerName:pithosContainer.name 
2023
                                                                                                                 blockSize:pithosContainer.blockSize 
2024
                                                                                                                   forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
2025
                                                                                                         missingBlockIndex:missingBlockIndex 
2026
                                                                                                            sharingAccount:nil];
2027
            newContainerRequest.delegate = self;
2028
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2029
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2030
            newContainerRequest.userInfo = objectRequest.userInfo;
2031
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
2032
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2033
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
2034
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2035
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
2036
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2037
                [activityFacility updateActivity:activity 
2038
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
2039
                                                  newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2040
                                      totalBytes:activity.totalBytes 
2041
                                    currentBytes:(activity.currentBytes + size)];
2042
            }];
2043
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2044
        }
2045
    } else {
2046
        [self requestFailed:objectRequest];
2047
    }
2048
    [pool drain];
2049
}
2050

    
2051
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
2052
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2053
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
2054
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
2055
    if (operation.isCancelled) {
2056
        [self requestFailed:containerRequest];
2057
    } else if (containerRequest.responseStatusCode == 202) {
2058
        ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
2059
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
2060
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
2061
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
2062
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
2063
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2064
        if (operation.isCancelled) {
2065
            [self requestFailed:containerRequest];
2066
        } else if (missingBlockIndex == NSNotFound) {
2067
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2068
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
2069
                                                                                           containerName:pithosContainer.name 
2070
                                                                                              objectName:object.name 
2071
                                                                                             contentType:object.contentType 
2072
                                                                                               blockSize:pithosContainer.blockSize 
2073
                                                                                               blockHash:pithosContainer.blockHash
2074
                                                                                                 forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2075
                                                                                           checkIfExists:NO 
2076
                                                                                                  hashes:&hashes 
2077
                                                                                          sharingAccount:nil];
2078
            newObjectRequest.delegate = self;
2079
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2080
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2081
            newObjectRequest.userInfo = containerRequest.userInfo;
2082
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2083
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2084
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2085
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2086
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2087
        } else {
2088
            if (newSyncRequested || syncLate || operation.isCancelled) {
2089
                [self requestFailed:containerRequest];
2090
            } else {
2091
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
2092
                                                                                                                 containerName:pithosContainer.name
2093
                                                                                                                     blockSize:pithosContainer.blockSize
2094
                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2095
                                                                                                             missingBlockIndex:missingBlockIndex 
2096
                                                                                                                sharingAccount:nil];
2097
                newContainerRequest.delegate = self;
2098
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2099
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2100
                newContainerRequest.userInfo = containerRequest.userInfo;
2101
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2102
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2103
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2104
                    [activityFacility updateActivity:activity 
2105
                                         withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
2106
                                                      newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2107
                                          totalBytes:activity.totalBytes 
2108
                                        currentBytes:(activity.currentBytes + size)];
2109
                }];
2110
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2111
            }
2112
        }
2113
    } else {
2114
        [self requestFailed:containerRequest];
2115
    }
2116
    [pool drain];
2117
}
2118

    
2119
- (void)requestFailed:(ASIPithosRequest *)request {
2120
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2121
    NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2122
    NSLog(@"Sync::request failed: %@", request.url);
2123
    if (operation.isCancelled) {
2124
        [pool drain];
2125
        return;        
2126
    }
2127
    if (request.isCancelled || newSyncRequested || syncLate) {
2128
        dispatch_async(dispatch_get_main_queue(), ^{
2129
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2130
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2131
        });
2132
        [self syncOperationFinishedWithSuccess:NO];
2133
        [pool drain];
2134
        return;
2135
    }
2136
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2137
    if (retries > 0) {
2138
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2139
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2140
        [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2141
    } else {
2142
        dispatch_async(dispatch_get_main_queue(), ^{
2143
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2144
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2145
        });
2146
        [self syncOperationFinishedWithSuccess:NO];
2147
    }
2148
    [pool drain];
2149
}
2150

    
2151
@end