Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ 02b6ea19

History | View | Annotate | Download (127.6 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, pithosAccount, containersDictionary, pithos;
77
@synthesize 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
       containersDictionary:(NSMutableDictionary *)aContainersDictionary 
87
            resetLocalState:(BOOL)resetLocalState {
88
    if ((self = [super init])) {
89
        directoryPath = [aDirectoryPath copy];
90
        pithosAccount = [aPithosAccount retain];
91
        containersDictionary = [aContainersDictionary retain];
92
        self.pithos = pithosAccount.pithos;
93
        
94
        containersCount = [containersDictionary count];
95
        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
96
        for (NSString *containerName in [containersDictionary allKeys]) {
97
            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
98
            pithosContainer.name = containerName;
99
            [pithosContainers addObject:pithosContainer];
100
        }
101
        
102
        activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
103
        
104
        if (resetLocalState)
105
            [self resetLocalStateWithAll:YES];
106
        else
107
            [self resetLocalStateWithAll:NO];
108
        
109
        networkQueue = [[ASINetworkQueue alloc] init];
110
        networkQueue.showAccurateProgress = YES;
111
        networkQueue.shouldCancelAllRequestsOnFailure = NO;
112
//        networkQueue.maxConcurrentOperationCount = 1;
113
        
114
        callbackQueue = [[NSOperationQueue alloc] init];
115
        [callbackQueue setSuspended:YES];
116
        callbackQueue.name = [NSString stringWithFormat:@"gr.grnet.pithos.SyncCallbackQueue.%@", pithosAccount.uniqueName];
117
//        callbackQueue.maxConcurrentOperationCount = 1;
118
        
119
        [[NSNotificationCenter defaultCenter] addObserver:self
120
                                                 selector:@selector(applicationWillTerminate:)
121
                                                     name:NSApplicationWillTerminateNotification
122
                                                   object:[NSApplication sharedApplication]];
123
    }
124
    return self;
125
}
126

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

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

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

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

    
242
- (void)startDaemon {
243
    @synchronized(self) {
244
        if (daemonActive)
245
            return;
246
    }
247

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

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

    
289
#pragma mark -
290
#pragma mark Observers
291

    
292
- (void)applicationWillTerminate:(NSNotification *)notification {
293
    [self saveLocalState];
294
}
295

    
296
#pragma mark -
297
#pragma mark Properties
298

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

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

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

    
363
- (void)setDirectoryPath:(NSString *)aDirectoryPath {
364
    if (aDirectoryPath && ![aDirectoryPath isEqualToString:directoryPath]) {
365
        [self resetDaemon];
366
        [self resetLocalStateWithAll:YES];
367
        [directoryPath release];
368
        directoryPath = [aDirectoryPath copy];
369
    }
370
}
371

    
372
- (void)setContainersDictionary:(NSDictionary *)aContainersDictionary {
373
    if (aContainersDictionary && ![aContainersDictionary isEqualToDictionary:containersDictionary]) {
374
        [self resetDaemon];
375
        [containersDictionary release];
376
        containersDictionary = [aContainersDictionary retain];
377
        containersCount = [containersDictionary count];
378
        self.pithosContainers = [NSMutableArray arrayWithCapacity:containersCount];
379
        for (NSString *containerName in [containersDictionary allKeys]) {
380
            ASIPithosContainer *pithosContainer = [ASIPithosContainer container];
381
            pithosContainer.name = containerName;
382
            [pithosContainers addObject:pithosContainer];
383
        }
384
        [self resetLocalStateWithAll:NO];
385
    }    
386
}
387

    
388
- (void)setPithos:(ASIPithos *)aPithos {
389
    if (!pithos) {
390
        pithos = [[ASIPithos pithos] retain];
391
        pithos.authUser = [[aPithos.authUser copy] autorelease];
392
        pithos.authToken = [[aPithos.authToken copy] autorelease];
393
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
394
        pithos.authURL = [[aPithos.authURL copy] autorelease];
395
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
396
    }
397
    if (aPithos && 
398
        (![aPithos.authUser isEqualToString:pithos.authUser] || 
399
         ![aPithos.authToken isEqualToString:pithos.authToken] || 
400
         ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])) {
401
        [self resetDaemon];
402
        if (![aPithos.authUser isEqualToString:pithos.authUser] || 
403
            ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix])
404
            [self resetLocalStateWithAll:YES];
405
        pithos.authUser = [[aPithos.authUser copy] autorelease];
406
        pithos.authToken = [[aPithos.authToken copy] autorelease];
407
        pithos.storageURLPrefix = [[aPithos.storageURLPrefix copy] autorelease];
408
        pithos.authURL = [[aPithos.authURL copy] autorelease];
409
        pithos.publicURLPrefix = [[aPithos.publicURLPrefix copy] autorelease];
410
    }        
411
}
412

    
413
#pragma mark -
414
#pragma mark Sync
415

    
416
- (void)syncOperationStarted {
417
    @synchronized(self) {
418
        syncOperationCount++;
419
    }
420
}
421

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

    
449
- (BOOL)isSyncing {
450
    @synchronized(self) {
451
        return ((syncOperationCount > 0) && daemonActive);
452
    }
453
}
454

    
455
- (void)syncLate {
456
    @synchronized(self) {
457
        if ([self isSyncing])
458
            syncLate = YES;
459
    }
460
}
461

    
462
- (void)sync {
463
    @synchronized(self) {
464
        if ([self isSyncing]) {
465
            // If at least one operation is running return
466
            newSyncRequested = YES;
467
            return;
468
        } else if (daemonActive && containersCount) {
469
            // The first operation is the server listing
470
            [self syncOperationStarted];
471
            newSyncRequested = NO;
472
            syncIncomplete = NO;
473
            syncLate = NO;
474
        } else {
475
            return;
476
        }
477
    }
478

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

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

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

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

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

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

    
1204
#pragma mark -
1205
#pragma mark ASIHTTPRequestDelegate
1206

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

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

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

    
1339
        dispatch_async(dispatch_get_main_queue(), ^{
1340
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1341
                              withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1342
        });
1343
        NSFileManager *fileManager = [NSFileManager defaultManager];
1344
        NSError *error;
1345
        NSMutableDictionary *subPaths = [NSMutableDictionary dictionaryWithCapacity:containersCount];
1346
        NSUInteger subPathsCount = 0;
1347
        NSString *containerDirectoryPath;
1348
        NSArray *containerSubPaths;
1349
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1350
            error = nil;
1351
            containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1352
            containerSubPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
1353
            if (error) {
1354
                dispatch_async(dispatch_get_main_queue(), ^{
1355
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
1356
                                                            message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
1357
                                                              error:error];
1358
                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
1359
                                                          message:@"Sync: Failed to read contents of sync directory" 
1360
                                                    pithosAccount:pithosAccount];
1361
                });
1362
                // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1363
                [self syncOperationFinishedWithSuccess:NO];
1364
                [pool drain];
1365
                return;
1366
            }
1367
            [subPaths setObject:containerSubPaths forKey:pithosContainer.name];
1368
            subPathsCount += [containerSubPaths count];
1369
        }        
1370
        
1371
        if (operation.isCancelled) {
1372
            operation.completionBlock = nil;
1373
            [self syncOperationFinishedWithSuccess:NO];
1374
            [pool drain];
1375
            return;
1376
        }
1377

    
1378
        self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:subPathsCount];
1379
        NSMutableDictionary *containerStoredLocalObjectStates;
1380
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1381
            containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1382
            containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1383
            for (NSString *objectName in [subPaths objectForKey:pithosContainer.name]) {
1384
                if (operation.isCancelled) {
1385
                    operation.completionBlock = nil;
1386
                    [self saveLocalState];
1387
                    [self syncOperationFinishedWithSuccess:NO];
1388
                    [pool drain];
1389
                    return;
1390
                }
1391

    
1392
                PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:objectName];
1393
                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1394
                if (!storedLocalObjectState || [storedLocalObjectState isModified]) {
1395
                    // New or modified existing local object, compute current state
1396
                    if (!storedLocalObjectState)
1397
                        // For new local object, also create empty stored state
1398
                        [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1399
                    [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
1400
                                                                                               blockHash:pithosContainer.blockHash 
1401
                                                                                               blockSize:pithosContainer.blockSize] 
1402
                                                          forKey:filePath];
1403
                } else {
1404
                    // Local object hasn't changed, set stored state also to current
1405
                    [currentLocalObjectStates setObject:storedLocalObjectState forKey:filePath];
1406
                }
1407
            }
1408
            [self saveLocalState];
1409
        }
1410
        
1411
        if (operation.isCancelled) {
1412
            operation.completionBlock = nil;
1413
            [self syncOperationFinishedWithSuccess:NO];
1414
            [pool drain];
1415
            return;
1416
        }
1417

    
1418
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1419
            containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1420
            for (NSString *objectName in [remoteObjects objectForKey:pithosContainer.name]) {
1421
                if (operation.isCancelled) {
1422
                    operation.completionBlock = nil;
1423
                    [self saveLocalState];
1424
                    [self syncOperationFinishedWithSuccess:NO];
1425
                    [pool drain];
1426
                    return;
1427
                }
1428

    
1429
                if (![containerStoredLocalObjectStates objectForKey:objectName])
1430
                    [containerStoredLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1431
            }
1432
            [self saveLocalState];
1433
        }
1434

    
1435
        if (operation.isCancelled) {
1436
            operation.completionBlock = nil;
1437
            [self syncOperationFinishedWithSuccess:NO];
1438
            [pool drain];
1439
            return;
1440
        }
1441

    
1442
        NSMutableDictionary *containerRemoteObjects;
1443
        for (ASIPithosContainer *pithosContainer in pithosContainers) {
1444
            containerDirectoryPath = [directoryPath stringByAppendingPathComponent:pithosContainer.name];
1445
            containerStoredLocalObjectStates = [storedLocalObjectStates objectForKey:pithosContainer.name];
1446
            containerRemoteObjects = [remoteObjects objectForKey:pithosContainer.name];
1447
            for (NSString *objectName in [[containerStoredLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1448
                if (operation.isCancelled) {
1449
                    operation.completionBlock = nil;
1450
                    [self syncOperationFinishedWithSuccess:NO];
1451
                    [pool drain];
1452
                    return;
1453
                }
1454

    
1455
                NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1456
                if ([objectName hasSuffix:@"/"])
1457
                    filePath = [filePath stringByAppendingString:@":"];
1458
                ASIPithosObject *object = [ASIPithosObject object];
1459
                object.name = objectName;
1460
                NSLog(@"Sync::object name: %@", object.name);
1461
            
1462
                PithosLocalObjectState *storedLocalObjectState = [containerStoredLocalObjectStates objectForKey:object.name];
1463
                PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1464
                if (!currentLocalObjectState)
1465
                    currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
1466
                                                                                     blockHash:pithosContainer.blockHash 
1467
                                                                                     blockSize:pithosContainer.blockSize];
1468
//                if (currentLocalObjectState.isDirectory)
1469
//                    object.contentType = @"application/directory";
1470
            
1471
                PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1472
                ASIPithosObject *remoteObject = [containerRemoteObjects objectForKey:object.name];
1473
                if (remoteObject) {
1474
                    if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1475
                        remoteObjectState.isDirectory = YES;
1476
//                        object.contentType = @"application/directory";
1477
                    } else {
1478
                        remoteObjectState.hash = remoteObject.objectHash;
1479
                    }
1480
                }
1481

    
1482
                BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1483
                BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1484
                NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1485
                if (!localStateHasChanged) {
1486
                    // Local state hasn't changed
1487
                    if (serverStateHasChanged) {
1488
                        // Server state has changed
1489
                        // Update local state to match that of the server 
1490
                        object.bytes = remoteObject.bytes;
1491
                        object.version = remoteObject.version;
1492
                        object.contentType = remoteObject.contentType;
1493
                        object.objectHash = remoteObject.objectHash;
1494
                        [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1495
                    } else if (!remoteObject && ![currentLocalObjectState exists]) {
1496
                        // Server state hasn't changed
1497
                        // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1498
                        [containerStoredLocalObjectStates removeObjectForKey:objectName];
1499
                        [self saveLocalState];
1500
                    }
1501
                } else {
1502
                    // Local state has changed
1503
                    if (!serverStateHasChanged) {
1504
                        // Server state hasn't changed
1505
                        if (currentLocalObjectState.isDirectory)
1506
                            object.contentType = @"application/directory";
1507
                        else
1508
                            object.objectHash = currentLocalObjectState.hash;
1509
                        [self updateServerStateWithCurrentState:currentLocalObjectState 
1510
                                                         object:object 
1511
                                                  localFilePath:filePath 
1512
                                                pithosContainer:pithosContainer];
1513
                    } else {
1514
                        // Server state has also changed
1515
                        if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1516
                            // Both did the same change (directory)
1517
                            storedLocalObjectState.filePath = filePath;
1518
                            storedLocalObjectState.isDirectory = YES;
1519
                            [self saveLocalState];
1520
                        } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1521
                            // Both did the same change (object edit or delete)
1522
                            if (![remoteObjectState exists]) {
1523
                                [containerStoredLocalObjectStates removeObjectForKey:object.name];
1524
                            } else {
1525
                                storedLocalObjectState.filePath = filePath;
1526
                                storedLocalObjectState.hash = remoteObjectState.hash;
1527
                            }
1528
                            [self saveLocalState];
1529
                        } else {
1530
                            // Conflict, we ask the user which change to keep
1531
                            NSString *informativeText;
1532
                            NSString *firstButtonText;
1533
                            NSString *secondButtonText;
1534
                            
1535
                            if (![remoteObjectState exists]) {
1536
                                // Remote object has been deleted
1537
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified locally, while it has been deleted from server.", 
1538
                                                   pithosContainer.name, object.name ];
1539
                                firstButtonText = @"Delete local file";
1540
                                secondButtonText = @"Upload file to server";
1541
                            } else if (![currentLocalObjectState exists]) {
1542
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified on the server, while it has been deleted locally.", 
1543
                                                   pithosContainer.name, object.name];
1544
                                firstButtonText = @"Download file from server";
1545
                                secondButtonText = @"Delete file on server";
1546
                            } else {
1547
                                informativeText = [NSString stringWithFormat:@"'%@/%@' has been modified both locally and on the server.", 
1548
                                                   pithosContainer.name, object.name];
1549
                                firstButtonText = @"Keep server version";
1550
                                secondButtonText = @"Keep local version";
1551
                            }
1552
                            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1553
                            [alert setMessageText:@"Conflict"];
1554
                            [alert setInformativeText:informativeText];
1555
                            [alert addButtonWithTitle:firstButtonText];
1556
                            [alert addButtonWithTitle:secondButtonText];
1557
                            [alert addButtonWithTitle:@"Do nothing"];
1558
                            NSInteger choice = [alert runModal];
1559
                            if (choice == NSAlertFirstButtonReturn) {
1560
                                object.bytes = remoteObject.bytes;
1561
                                object.version = remoteObject.version;
1562
                                object.contentType = remoteObject.contentType;
1563
                                object.objectHash = remoteObject.objectHash;
1564
                                [self updateLocalStateWithObject:object localFilePath:filePath pithosContainer:pithosContainer];
1565
                            } if (choice == NSAlertSecondButtonReturn) {
1566
                                if (currentLocalObjectState.isDirectory)
1567
                                    object.contentType = @"application/directory";
1568
                                else
1569
                                    object.objectHash = currentLocalObjectState.hash;
1570
                                [self updateServerStateWithCurrentState:currentLocalObjectState 
1571
                                                                 object:object 
1572
                                                          localFilePath:filePath 
1573
                                                        pithosContainer:pithosContainer];
1574
                            }
1575
                        }
1576
                    }
1577
                }
1578
            }
1579
        }
1580
        [self syncOperationFinishedWithSuccess:YES];
1581
    } else {
1582
        [self listRequestFailed:containerRequest];
1583
    }
1584
    [pool drain];
1585
}
1586

    
1587
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1588
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1589
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1590
    if (operation.isCancelled) {
1591
        [objects release];
1592
        objects = nil;
1593
        [pool drain];
1594
        return;        
1595
    }
1596
    if (containerRequest.isCancelled) {
1597
        dispatch_async(dispatch_get_main_queue(), ^{
1598
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1599
                              withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1600
        });
1601
        [objects release];
1602
        objects = nil;
1603
        [self syncOperationFinishedWithSuccess:NO];
1604
        [pool drain];
1605
        return;
1606
    }
1607
    // If the server listing fails, the sync should start over, so just retrying is enough
1608
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1609
    if (retries > 0) {
1610
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1611
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1612
        [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1613
    } else {
1614
        dispatch_async(dispatch_get_main_queue(), ^{
1615
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1616
                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1617
        });
1618
        [objects release];
1619
        objects = nil;
1620
        // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1621
        [self syncOperationFinishedWithSuccess:NO];
1622
    }
1623
    [pool drain];
1624
}
1625

    
1626
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1627
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1628
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1629
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1630
    if (operation.isCancelled) {
1631
        [self requestFailed:objectRequest];
1632
    } else if (objectRequest.responseStatusCode == 206) {
1633
        ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1634
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1635
        NSFileManager *fileManager = [NSFileManager defaultManager];
1636
        NSError *error;
1637
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1638
        
1639
        NSString *downloadsDirPath = self.tempDownloadsDirPath;
1640
        if (!downloadsDirPath) {
1641
            dispatch_async(dispatch_get_main_queue(), ^{
1642
                [activityFacility endActivity:activity 
1643
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1644
            });
1645
            [self syncOperationFinishedWithSuccess:NO];
1646
            [pool drain];
1647
            return;
1648
        }
1649
        
1650
        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1651
        if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1652
            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1653
            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1654
            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1655
            strcpy(tempFileNameCString, tempFileTemplateCString);
1656
            int fileDescriptor = mkstemp(tempFileNameCString);
1657
            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1658
            free(tempFileNameCString);
1659
            if (fileDescriptor == -1) {
1660
                dispatch_async(dispatch_get_main_queue(), ^{
1661
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1662
                                                            message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath] 
1663
                                                              error:nil];
1664
                    [activityFacility endActivity:activity 
1665
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1666
                });
1667
                [self syncOperationFinishedWithSuccess:NO];
1668
                [pool drain];
1669
                return;
1670
            }
1671
            close(fileDescriptor);
1672
            storedState.tmpFilePath = tempFilePath;
1673
            [self saveLocalState];
1674
        }
1675
        
1676

    
1677
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1678
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1679
        [tempFileHandle seekToFileOffset:missingBlockIndex*pithosContainer.blockSize];
1680
        [tempFileHandle writeData:[objectRequest responseData]];
1681
        [tempFileHandle closeFile];
1682

    
1683
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1684
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1685
        if (missingBlockIndex == NSNotFound) {
1686
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1687
            NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1688
            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath pithosContainer:pithosContainer]) {
1689
                dispatch_async(dispatch_get_main_queue(), ^{
1690
                    [activityFacility endActivity:activity 
1691
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1692
                });
1693
                [self syncOperationFinishedWithSuccess:NO];
1694
                [pool drain];
1695
                return;
1696
            } else if (![fileManager fileExistsAtPath:dirPath]) {
1697
                // File doesn't exist but also the containing directory doesn't exist
1698
                // In most cases this should have been resolved as an update of the corresponding local object,
1699
                // but it never hurts to check
1700
                error = nil;
1701
                [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1702
                if (error != nil) {
1703
                    dispatch_async(dispatch_get_main_queue(), ^{
1704
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1705
                                                                message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1706
                                                                  error:error];
1707
                        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1708
                                          withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1709
                    });
1710
                    [self syncOperationFinishedWithSuccess:NO];
1711
                    [pool drain];
1712
                    return;
1713
                }
1714
            }
1715
            // Move file from tmp download
1716
            error = nil;
1717
            [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1718
            if (error != nil) {
1719
                dispatch_async(dispatch_get_main_queue(), ^{
1720
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1721
                                                            message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath] 
1722
                                                              error:error];
1723
                    [activityFacility endActivity:activity 
1724
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1725
                });
1726
                [self syncOperationFinishedWithSuccess:NO];
1727
                [pool drain];
1728
                return;
1729
            }
1730
            dispatch_async(dispatch_get_main_queue(), ^{
1731
                [activityFacility endActivity:activity 
1732
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1733
                                   totalBytes:activity.totalBytes 
1734
                                 currentBytes:activity.totalBytes];
1735
            });
1736

    
1737
            storedState.filePath = filePath;
1738
            storedState.hash = object.objectHash;
1739
            storedState.tmpFilePath = nil;
1740
            [self saveLocalState];
1741
            [self syncOperationFinishedWithSuccess:YES];
1742
            [pool drain];
1743
            return;
1744
        } else {
1745
            if (newSyncRequested || syncLate || operation.isCancelled) {
1746
                [self requestFailed:objectRequest];
1747
            } else {
1748
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1749
                                                                                                       containerName:pithosContainer.name 
1750
                                                                                                              object:object 
1751
                                                                                                          blockIndex:missingBlockIndex 
1752
                                                                                                           blockSize:pithosContainer.blockSize];
1753
                newObjectRequest.delegate = self;
1754
                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1755
                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1756
                newObjectRequest.userInfo = objectRequest.userInfo;
1757
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1758
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1759
                [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1760
                    [activityFacility updateActivity:activity 
1761
                                         withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1762
                                                      newObjectRequest.containerName, object.name, 
1763
                                                      (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1764
                                          totalBytes:activity.totalBytes 
1765
                                        currentBytes:(activity.currentBytes + size)];
1766
                }];
1767
                [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1768
            }
1769
        }
1770
    } else if (objectRequest.responseStatusCode == 412) {
1771
        // The object has changed on the server
1772
        dispatch_async(dispatch_get_main_queue(), ^{
1773
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1774
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1775
        });
1776
        [self syncOperationFinishedWithSuccess:NO];
1777
    } else {
1778
        [self requestFailed:objectRequest];
1779
    }
1780
    [pool drain];
1781
}
1782

    
1783
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1784
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1785
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1786
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1787
    if (operation.isCancelled) {
1788
        [self requestFailed:objectRequest];
1789
    } else if (objectRequest.responseStatusCode == 200) {
1790
        if (newSyncRequested || syncLate || operation.isCancelled) {
1791
            [self requestFailed:objectRequest];
1792
        } else {
1793
            ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1794
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1795
            PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1796
            if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1797
                [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1798
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1799
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath 
1800
                                                                    blockSize:pithosContainer.blockSize 
1801
                                                                    blockHash:pithosContainer.blockHash 
1802
                                                                   withHashes:[objectRequest hashes]];
1803
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1804
            dispatch_async(dispatch_get_main_queue(), ^{
1805
                [activityFacility updateActivity:activity 
1806
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1807
                                                  pithosContainer.name, object.name, 
1808
                                                  (100*(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1809
                                      totalBytes:activity.totalBytes 
1810
                                    currentBytes:(activity.totalBytes - [missingBlocks count]*pithosContainer.blockSize)];
1811
            });
1812

    
1813
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1814
                                                                                                   containerName:pithosContainer.name 
1815
                                                                                                          object:object 
1816
                                                                                                      blockIndex:missingBlockIndex 
1817
                                                                                                       blockSize:pithosContainer.blockSize];
1818
            newObjectRequest.delegate = self;
1819
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1820
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1821
            newObjectRequest.userInfo = objectRequest.userInfo;
1822
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1823
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1824
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1825
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1826
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1827
                [activityFacility updateActivity:activity 
1828
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@/%@' (%.0f%%)", 
1829
                                                  newObjectRequest.containerName, object.name, 
1830
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1831
                                      totalBytes:activity.totalBytes 
1832
                                    currentBytes:(activity.currentBytes + size)];
1833
            }];
1834
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1835
        }
1836
    } else {
1837
        [self requestFailed:objectRequest];
1838
    }
1839
    [pool drain];
1840
}
1841

    
1842
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1843
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1844
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1845
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1846
    if (operation.isCancelled) {
1847
        [self requestFailed:objectRequest];
1848
    } else if (objectRequest.responseStatusCode == 201) {
1849
        PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1850
                                               objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1851
        storedState.isDirectory = YES;
1852
        [self saveLocalState];
1853
        dispatch_async(dispatch_get_main_queue(), ^{
1854
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1855
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1856
        });
1857
        [self syncOperationFinishedWithSuccess:YES];
1858
    } else {
1859
        [self requestFailed:objectRequest];
1860
    }
1861
    [pool drain];
1862
}
1863

    
1864
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1865
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1866
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1867
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1868
    if (operation.isCancelled) {
1869
        [self requestFailed:objectRequest];
1870
    } else if (objectRequest.responseStatusCode == 201) {
1871
        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1872
         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1873
        [self saveLocalState];
1874
        dispatch_async(dispatch_get_main_queue(), ^{
1875
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1876
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1877
        });
1878
        [self syncOperationFinishedWithSuccess:YES];
1879
    } else {
1880
        [self requestFailed:objectRequest];
1881
    }
1882
    [pool drain];
1883
}
1884

    
1885
- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1886
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1887
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1888
    NSLog(@"Sync::delete object finished: %@", objectRequest.url);
1889
    if (operation.isCancelled) {
1890
        [self requestFailed:objectRequest];
1891
    } else if (objectRequest.responseStatusCode == 204) {
1892
        [[storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosContainer"] name]] 
1893
         removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1894
        [self saveLocalState];
1895
        dispatch_async(dispatch_get_main_queue(), ^{
1896
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1897
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1898
        });
1899
        [self syncOperationFinishedWithSuccess:YES];
1900
    } else {
1901
        [self requestFailed:objectRequest];
1902
    }
1903
    [pool drain];
1904
}
1905

    
1906
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1907
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1908
    NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1909
    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1910
    ASIPithosContainer *pithosContainer = [objectRequest.userInfo objectForKey:@"pithosContainer"];
1911
    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1912
    PithosLocalObjectState *storedState = [[storedLocalObjectStates objectForKey:pithosContainer.name] objectForKey:object.name];
1913
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1914
    NSUInteger totalBytes = activity.totalBytes;
1915
    NSUInteger currentBytes = activity.currentBytes;
1916
    if (operation.isCancelled) {
1917
        [self requestFailed:objectRequest];
1918
    } else if (objectRequest.responseStatusCode == 201) {
1919
        NSLog(@"Sync::object created: %@", objectRequest.url);
1920
        storedState.filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1921
        storedState.hash = object.objectHash;
1922
        [self saveLocalState];
1923
        dispatch_async(dispatch_get_main_queue(), ^{
1924
            [activityFacility endActivity:activity 
1925
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1926
                               totalBytes:totalBytes 
1927
                             currentBytes:totalBytes];
1928
        });
1929
        [self syncOperationFinishedWithSuccess:YES];
1930
    } else if (objectRequest.responseStatusCode == 409) {
1931
        if (newSyncRequested || syncLate || operation.isCancelled) {
1932
            [self requestFailed:objectRequest];
1933
        } else {
1934
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1935
            if (iteration == 0) {
1936
                NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1937
                dispatch_async(dispatch_get_main_queue(), ^{
1938
                    [activityFacility endActivity:activity withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1939
                });
1940
                [self syncOperationFinishedWithSuccess:NO];
1941
                [pool drain];
1942
                return;
1943
            }
1944
            NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1945
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1946
                                                              withMissingHashes:[objectRequest hashes]];
1947
            if (totalBytes >= [missingBlocks count]*pithosContainer.blockSize)
1948
                currentBytes = totalBytes - [missingBlocks count]*pithosContainer.blockSize;
1949
            dispatch_async(dispatch_get_main_queue(), ^{
1950
                [activityFacility updateActivity:activity 
1951
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
1952
                                                  pithosContainer.name, object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1953
                                      totalBytes:totalBytes 
1954
                                    currentBytes:currentBytes];
1955
            });
1956
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1957
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
1958
                                                                                                             containerName:pithosContainer.name 
1959
                                                                                                                 blockSize:pithosContainer.blockSize 
1960
                                                                                                                   forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1961
                                                                                                         missingBlockIndex:missingBlockIndex 
1962
                                                                                                            sharingAccount:nil];
1963
            newContainerRequest.delegate = self;
1964
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1965
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1966
            newContainerRequest.userInfo = objectRequest.userInfo;
1967
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1968
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1969
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1970
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1971
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1972
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1973
                [activityFacility updateActivity:activity 
1974
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
1975
                                                  newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1976
                                      totalBytes:activity.totalBytes 
1977
                                    currentBytes:(activity.currentBytes + size)];
1978
            }];
1979
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1980
        }
1981
    } else {
1982
        [self requestFailed:objectRequest];
1983
    }
1984
    [pool drain];
1985
}
1986

    
1987
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1988
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1989
    NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1990
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1991
    if (operation.isCancelled) {
1992
        [self requestFailed:containerRequest];
1993
    } else if (containerRequest.responseStatusCode == 202) {
1994
        ASIPithosContainer *pithosContainer = [containerRequest.userInfo objectForKey:@"pithosContainer"];
1995
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1996
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1997
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1998
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1999
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
2000
        if (operation.isCancelled) {
2001
            [self requestFailed:containerRequest];
2002
        } else if (missingBlockIndex == NSNotFound) {
2003
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
2004
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos 
2005
                                                                                           containerName:pithosContainer.name 
2006
                                                                                              objectName:object.name 
2007
                                                                                             contentType:object.contentType 
2008
                                                                                               blockSize:pithosContainer.blockSize 
2009
                                                                                               blockHash:pithosContainer.blockHash
2010
                                                                                                 forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2011
                                                                                           checkIfExists:NO 
2012
                                                                                                  hashes:&hashes 
2013
                                                                                          sharingAccount:nil];
2014
            newObjectRequest.delegate = self;
2015
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2016
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2017
            newObjectRequest.userInfo = containerRequest.userInfo;
2018
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2019
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
2020
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
2021
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
2022
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
2023
        } else {
2024
            if (newSyncRequested || syncLate || operation.isCancelled) {
2025
                [self requestFailed:containerRequest];
2026
            } else {
2027
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos 
2028
                                                                                                                 containerName:pithosContainer.name
2029
                                                                                                                     blockSize:pithosContainer.blockSize
2030
                                                                                                                       forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
2031
                                                                                                             missingBlockIndex:missingBlockIndex 
2032
                                                                                                                sharingAccount:nil];
2033
                newContainerRequest.delegate = self;
2034
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2035
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2036
                newContainerRequest.userInfo = containerRequest.userInfo;
2037
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
2038
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
2039
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
2040
                    [activityFacility updateActivity:activity 
2041
                                         withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@/%@' (%.0f%%)", 
2042
                                                      newContainerRequest.containerName, object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
2043
                                          totalBytes:activity.totalBytes 
2044
                                        currentBytes:(activity.currentBytes + size)];
2045
                }];
2046
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
2047
            }
2048
        }
2049
    } else {
2050
        [self requestFailed:containerRequest];
2051
    }
2052
    [pool drain];
2053
}
2054

    
2055
- (void)requestFailed:(ASIPithosRequest *)request {
2056
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2057
    NSOperation *operation = [request.userInfo objectForKey:@"operation"];
2058
    NSLog(@"Sync::request failed: %@", request.url);
2059
    if (operation.isCancelled) {
2060
        [pool drain];
2061
        return;        
2062
    }
2063
    if (request.isCancelled || newSyncRequested || syncLate) {
2064
        dispatch_async(dispatch_get_main_queue(), ^{
2065
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2066
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
2067
        });
2068
        [self syncOperationFinishedWithSuccess:NO];
2069
        [pool drain];
2070
        return;
2071
    }
2072
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
2073
    if (retries > 0) {
2074
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
2075
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
2076
        [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
2077
    } else {
2078
        dispatch_async(dispatch_get_main_queue(), ^{
2079
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
2080
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
2081
        });
2082
        [self syncOperationFinishedWithSuccess:NO];
2083
    }
2084
    [pool drain];
2085
}
2086

    
2087
@end