Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ 54037f6f

History | View | Annotate | Download (102.6 kB)

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

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

    
48
@interface PithosSyncDaemon (Private)
49
- (NSString *)pithosStateFilePath;
50
- (void)saveLocalState;
51

    
52
- (BOOL)moveToTempTrashFile:(NSString *)filePath;
53
- (void)emptyTempTrash;
54
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath;
55

    
56
- (void)updateLocalStateWithObject:(ASIPithosObject *)object localFilePath:(NSString *)filePath;
57
- (void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
58
                                   object:(ASIPithosObject *)object 
59
                            localFilePath:(NSString *)filePath;
60
- (void)requestFailed:(ASIPithosRequest *)request;
61

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

    
65
@end
66

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

    
71
#pragma mark -
72
#pragma Object Lifecycle
73

    
74
- (id)initWithDirectoryPath:(NSString *)aDirectoryPath 
75
              containerName:(NSString *)aContainerName 
76
               timeInterval:(NSTimeInterval)aTimeInterval 
77
            resetLocalState:(BOOL)resetLocalState {
78
    if ((self = [super init])) {
79
        directoryPath = [aDirectoryPath copy];
80
        containerName = [aContainerName copy];
81
        timeInterval = aTimeInterval;
82
        
83
        syncOperationCount = 0;
84
        newSyncRequested = NO;
85
        containerDirectoryPath = [[directoryPath stringByAppendingPathComponent:containerName] copy];
86
        
87
        activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
88
        
89
        NSFileManager *fileManager = [NSFileManager defaultManager];
90
        if (resetLocalState) {
91
            NSError *error = nil;
92
            if ([fileManager fileExistsAtPath:self.pithosStateFilePath] && 
93
                (![fileManager removeItemAtPath:self.pithosStateFilePath error:&error] || error))
94
                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
95
                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", self.pithosStateFilePath] 
96
                                                          error:error];
97
            if (self.tempDownloadsDirPath) {
98
                error = nil;
99
                for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:self.tempDownloadsDirPath error:&error]) {
100
                    if (error) {
101
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
102
                                                                message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", self.tempDownloadsDirPath] 
103
                                                                  error:error];
104
                        break;
105
                    }
106
                    NSString *subFilePath = [self.tempDownloadsDirPath stringByAppendingPathComponent:subPath];
107
                    if (![fileManager removeItemAtPath:subFilePath error:&error] || error) {
108
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
109
                                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
110
                                                                  error:error];
111
                    }
112
                    error = nil;
113
                }
114
            }
115
        }
116
        if ([[NSFileManager defaultManager] fileExistsAtPath:self.pithosStateFilePath]) {
117
            NSData *data = [NSData dataWithContentsOfFile:self.pithosStateFilePath];
118
            NSKeyedUnarchiver *unarchiver = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
119
            self.storedLocalObjectStates = [unarchiver decodeObjectForKey:containerName];
120
            [unarchiver finishDecoding];
121
        } else {
122
            self.storedLocalObjectStates = [NSMutableDictionary dictionary];
123
        }
124
        
125
        networkQueue = [[ASINetworkQueue alloc] init];
126
        networkQueue.showAccurateProgress = YES;
127
        networkQueue.shouldCancelAllRequestsOnFailure = NO;
128
        [networkQueue go];
129
        
130
        queue = dispatch_queue_create("gr.grnet.pithos.SyncQueue", NULL);
131
        
132
        [[NSNotificationCenter defaultCenter] addObserver:self
133
                                                 selector:@selector(applicationWillTerminate:)
134
                                                     name:NSApplicationWillTerminateNotification
135
                                                   object:[NSApplication sharedApplication]];
136
    
137
        timer = [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self selector:@selector(sync) userInfo:nil repeats:YES] retain];
138
        [timer fire];
139
    }
140
    
141
    return self;
142
}
143

    
144
- (void)dealloc {
145
    [[NSNotificationCenter defaultCenter] removeObserver:self];
146
    dispatch_release(queue);
147
    [networkQueue cancelAllOperations];
148
    [networkQueue release];
149
    [timer invalidate];
150
    [timer release];
151
    [tempTrashDirPath release];
152
    [tempDownloadsDirPath release];
153
    [pithosStateFilePath release];
154
    [containerDirectoryPath release];
155
    [currentLocalObjectStates release];
156
    [storedLocalObjectStates release];
157
    [remoteObjects release];
158
    [objects release];
159
    [lastCompletedSync release];
160
    [lastModified release];
161
    [blockHash release];
162
    [containerName release];
163
    [directoryPath release];
164
    [super dealloc];
165
}
166

    
167
#pragma mark -
168
#pragma mark Observers
169

    
170
- (void)applicationWillTerminate:(NSNotification *)notification {
171
    [self saveLocalState];
172
}
173

    
174
#pragma mark -
175
#pragma mark Properties
176

    
177
- (NSString *)pithosStateFilePath {
178
    if (!pithosStateFilePath)
179
        pithosStateFilePath = [[[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
180
                                 stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
181
                                stringByAppendingPathComponent:@"PithosLocalObjectStates.archive"] retain];
182
    return [[pithosStateFilePath copy] autorelease];
183
}
184

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

    
222
- (NSString *)tempTrashDirPath {
223
    NSFileManager *fileManager = [NSFileManager defaultManager];
224
    if (!tempTrashDirPath || ![fileManager fileExistsAtPath:tempTrashDirPath]) {
225
        // Get the path from user defaults
226
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
227
        tempTrashDirPath = [userDefaults stringForKey:@"PithosSyncTempTrashDirPath"];
228
        if (tempTrashDirPath) {
229
            // Check if the path exists
230
            BOOL isDirectory;
231
            BOOL fileExists = [fileManager fileExistsAtPath:tempTrashDirPath isDirectory:&isDirectory];
232
            NSError *error = nil;
233
            if (fileExists && !isDirectory)
234
                [fileManager removeItemAtPath:tempTrashDirPath error:&error];
235
            if (!error & !fileExists)
236
                [fileManager createDirectoryAtPath:tempTrashDirPath withIntermediateDirectories:YES attributes:nil error:&error];
237
            if (error)
238
                tempTrashDirPath = nil;
239
        }
240
        if (!tempTrashDirPath) {
241
            NSString *tempDirTemplate = [[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] 
242
                                          stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]] 
243
                                         stringByAppendingPathComponent:@"Temp Trash XXXXXX"];
244
            const char *tempDirTemplateCString = [tempDirTemplate fileSystemRepresentation];
245
            char *tempDirNameCString = (char *)malloc(strlen(tempDirTemplateCString) + 1);
246
            strcpy(tempDirNameCString, tempDirTemplateCString);
247
            tempDirNameCString = mkdtemp(tempDirNameCString);
248
            if (tempDirNameCString != NULL)
249
                tempTrashDirPath = [fileManager stringWithFileSystemRepresentation:tempDirNameCString length:strlen(tempDirNameCString)];
250
            free(tempDirNameCString);
251
        }
252
        if (tempTrashDirPath)
253
            [userDefaults setObject:tempTrashDirPath forKey:@"PithosSyncTempTrashDirPath"];
254
        [tempTrashDirPath retain];
255
    }
256
    return [[tempTrashDirPath copy] autorelease];
257
}
258

    
259
#pragma mark -
260
#pragma mark Sync
261

    
262
- (void)saveLocalState {
263
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
264
    NSMutableData *data = [NSMutableData data];
265
    NSKeyedArchiver *archiver = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
266
    [archiver encodeObject:storedLocalObjectStates forKey:containerName];
267
    [archiver finishEncoding];
268
    [data writeToFile:self.pithosStateFilePath atomically:YES];
269
    [pool drain];
270
}
271

    
272
- (void)increaseSyncOperationCount {
273
    @synchronized(self) {
274
        syncOperationCount++;
275
    }
276
}
277

    
278
- (void)decreaseSyncOperationCount {
279
    @synchronized(self) {
280
        syncOperationCount--;
281
        if (!syncOperationCount) {
282
            if (!syncIncomplete) {
283
                self.lastCompletedSync = [NSDate date];
284
                dispatch_async(dispatch_get_main_queue(), ^{
285
                    [activityFacility startAndEndActivityWithType:PithosActivityOther 
286
                                                          message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
287

    
288
                });
289
            }
290
            [self emptyTempTrash];
291
        }
292
    }
293
}
294

    
295
- (void)sync {
296
    @synchronized(self) {
297
        if (syncOperationCount) {
298
            // If at least one operation is running return
299
            newSyncRequested = YES;
300
            return;
301
        } else {
302
            // The first operation is the server listing
303
            syncOperationCount = 1;
304
            newSyncRequested = NO;
305
            syncIncomplete = NO;
306
        }
307
    }
308

    
309
    NSFileManager *fileManager = [NSFileManager defaultManager];
310
    BOOL isDirectory;
311
    NSError *error = nil;
312
    if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
313
        if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
314
            error) {
315
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
316
                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath] 
317
                                                      error:error];
318
            @synchronized(self) {
319
                syncOperationCount = 0;
320
            }
321
            return;
322
        }
323
    } else if (!isDirectory) {
324
        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
325
                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath] 
326
                                                  error:nil];
327
        @synchronized(self) {
328
            syncOperationCount = 0;
329
        }
330
        return;
331
    }
332
    
333
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
334
                                                                                                           limit:0 
335
                                                                                                          marker:nil 
336
                                                                                                          prefix:nil 
337
                                                                                                       delimiter:nil 
338
                                                                                                            path:nil 
339
                                                                                                            meta:nil 
340
                                                                                                          shared:NO 
341
                                                                                                           until:nil 
342
                                                                                                 ifModifiedSince:lastModified];
343
    containerRequest.delegate = self;
344
    containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
345
    containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
346
    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
347
                                                               message:@"Sync: Getting server listing"];
348
    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
349
                                 activity, @"activity", 
350
                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
351
                                 @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
352
                                 @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
353
                                 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
354
                                 [NSNumber numberWithUnsignedInteger:10], @"retries", 
355
                                 NSStringFromSelector(@selector(listRequestFinished:)), @"didFinishSelector", 
356
                                 NSStringFromSelector(@selector(listRequestFailed:)), @"didFailSelector", 
357
                                 nil];
358
    [networkQueue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityHigh]];
359
}
360

    
361
- (void)emptyTempTrash {
362
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
363
    NSString *trashDirPath = self.tempTrashDirPath;
364
    if (trashDirPath) {
365
        NSFileManager *fileManager = [NSFileManager defaultManager];
366
        NSError *error = nil;
367
//        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
368
        NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
369
        if (error) {
370
            dispatch_async(dispatch_get_main_queue(), ^{
371
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
372
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
373
                                                          error:error];
374
            });
375
            [pool drain];
376
            return;
377
        }
378
        if ([subPaths count]) {
379
//            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
380
//            for (NSString *subPath in subPaths) {
381
//                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
382
//            }
383
//            syncOperationCount = 1;
384
//            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
385
//                if (error) {
386
//                    dispatch_async(dispatch_get_main_queue(), ^{
387
//                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
388
//                                                                message:@"Cannot move files to Trash" 
389
//                                                                  error:error];
390
//                    });
391
//                }
392
//                syncOperationCount = 0;
393
//            }];
394
            for (NSString *subPath in subPaths) {
395
                NSString *subFilePath = [trashDirPath stringByAppendingPathComponent:subPath];
396
                error = nil;
397
                if (![fileManager removeItemAtPath:subFilePath error:&error] || error)
398
                    dispatch_async(dispatch_get_main_queue(), ^{
399
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
400
                                                                message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] 
401
                                                                  error:error];
402
                    });
403
            }
404
        }
405
    }
406
    [pool drain];
407
}
408

    
409
- (BOOL)moveToTempTrashFile:(NSString *)filePath {
410
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
411
    NSString *trashDirPath = self.tempTrashDirPath;
412
    if (!tempTrashDirPath) {
413
        [pool drain];
414
        return NO;
415
    }
416
    NSFileManager *fileManager = [NSFileManager defaultManager];
417
    BOOL isDirectory;
418
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
419
    NSError *error = nil;
420
    NSString *newFilePath = [filePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
421
                                                                withString:trashDirPath];
422
    NSString *newDirPath = [newFilePath stringByDeletingLastPathComponent];
423
    if (fileExists && isDirectory) {
424
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:filePath error:&error];
425
        if (error) {
426
            dispatch_async(dispatch_get_main_queue(), ^{
427
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
428
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
429
                                                          error:error];
430
            });
431
            [pool drain];
432
            return NO;
433
        }
434
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
435
            dispatch_async(dispatch_get_main_queue(), ^{
436
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
437
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
438
                                                          error:error];
439
            });
440
            [pool drain];
441
            return NO;
442
        }
443
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
444
            dispatch_async(dispatch_get_main_queue(), ^{
445
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
446
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
447
                                                                 filePath, newFilePath] 
448
                                                          error:error];
449
            });
450
            [pool drain];
451
            return NO;
452
        }
453
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
454
        if (currentState) {
455
            currentState.filePath = newFilePath;
456
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
457
            [currentLocalObjectStates removeObjectForKey:filePath];        
458
        } else {
459
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
460
                                                                                       blockHash:blockHash 
461
                                                                                       blockSize:blockSize] 
462
                                         forKey:newFilePath];
463
        }
464
        for (NSString *subPath in subPaths) {
465
            NSString *subFilePath = [filePath stringByAppendingPathComponent:subPath];
466
            NSString *newSubFilePath = [subFilePath stringByReplacingOccurrencesOfString:containerDirectoryPath 
467
                                                                              withString:trashDirPath];
468
            currentState = [currentLocalObjectStates objectForKey:subFilePath];
469
            if (currentState) {
470
                currentState.filePath = newSubFilePath;
471
                [currentLocalObjectStates setObject:currentState forKey:newSubFilePath];
472
                [currentLocalObjectStates removeObjectForKey:subFilePath];
473
            } else {
474
                [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newSubFilePath 
475
                                                                                           blockHash:blockHash 
476
                                                                                           blockSize:blockSize] 
477
                                             forKey:newSubFilePath];
478
            }        
479
        }
480
    } else if (fileExists && !isDirectory) {
481
        if (![fileManager createDirectoryAtPath:newDirPath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
482
            dispatch_async(dispatch_get_main_queue(), ^{
483
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
484
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", newDirPath] 
485
                                                          error:error];
486
            });
487
            [pool drain];
488
            return NO;
489
        }
490
        if (![fileManager moveItemAtPath:filePath toPath:newFilePath error:&error] || error) {
491
            dispatch_async(dispatch_get_main_queue(), ^{
492
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
493
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
494
                                                                 filePath, newFilePath] 
495
                                                          error:error];
496
            });
497
            [pool drain];
498
            return NO;
499
        }
500
        PithosLocalObjectState *currentState = [currentLocalObjectStates objectForKey:filePath];
501
        if (currentState) {
502
            currentState.filePath = newFilePath;
503
            [currentLocalObjectStates setObject:currentState forKey:newFilePath];
504
            [currentLocalObjectStates removeObjectForKey:filePath];        
505
        } else {
506
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:newFilePath 
507
                                                                                       blockHash:blockHash 
508
                                                                                       blockSize:blockSize] 
509
                                         forKey:newFilePath];
510
        }
511
    }
512
    [pool drain];
513
    return YES;
514
}
515

    
516
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
517
    if ([hash length] != 64)
518
        return NO;
519
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
520
    PithosLocalObjectState *localState;
521
    NSFileManager *fileManager = [NSFileManager defaultManager];
522
    BOOL isDirectory;
523
    NSError *error = nil;
524
    for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
525
        localState = [currentLocalObjectStates objectForKey:localFilePath];
526
        if (!localState.isDirectory && [hash isEqualToString:localState.hash] && 
527
            [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
528
            if ([localFilePath hasPrefix:containerDirectoryPath]) {
529
                if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
530
                    dispatch_async(dispatch_get_main_queue(), ^{
531
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
532
                                                                message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
533
                                                                         localFilePath, filePath] 
534
                                                                  error:error];
535
                    });
536
                } else {
537
                    [pool drain];
538
                    return YES;
539
                }            
540
            } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
541
                if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
542
                    dispatch_async(dispatch_get_main_queue(), ^{
543
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
544
                                                                message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
545
                                                                         localFilePath, filePath] 
546
                                                                  error:error];
547
                    });
548
                } else {
549
                    localState.filePath = filePath;
550
                    [currentLocalObjectStates setObject:localState forKey:filePath];
551
                    [currentLocalObjectStates removeObjectForKey:localFilePath];
552
                    [pool drain];
553
                    return YES;
554
                }
555
            }
556
        }
557
    }
558
    [pool drain];
559
    return NO;
560
}
561

    
562
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
563
                     localFilePath:(NSString *)filePath {
564
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
565
    NSFileManager *fileManager = [NSFileManager defaultManager];
566
    NSError *error;
567
    BOOL isDirectory;
568
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
569
    NSString *fileDirectoryPath = [filePath stringByDeletingLastPathComponent];
570
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
571
    // Remote updated info
572
    NSError *remoteError;
573
    BOOL remoteIsDirectory;
574
    BOOL remoteObjectExists = [PithosUtilities objectExistsAtContainerName:containerName 
575
                                                                objectName:object.name 
576
                                                                     error:&remoteError 
577
                                                               isDirectory:&remoteIsDirectory 
578
                                                            sharingAccount:nil];
579
    if (!object || !object.objectHash) {
580
        // Delete local object
581
        if (remoteObjectExists) {
582
            // Remote object created in the meantime, just mark the sync cycle as incomplete, but do delete the local object
583
            syncIncomplete = YES;
584
        }
585
        NSLog(@"Sync::delete local object: %@", filePath);
586
        if (!fileExists || [self moveToTempTrashFile:filePath]) {
587
            dispatch_async(dispatch_get_main_queue(), ^{
588
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
589
                                                      message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
590
            });
591
            [storedLocalObjectStates removeObjectForKey:object.name];
592
            [self saveLocalState];
593
        }
594
    } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
595
        // Create local directory object
596
        if (!remoteObjectExists || !remoteIsDirectory) {
597
            // Remote directory object deleted or changed to a file object in the meantime, mark the sync cycle as incomplete and skip
598
            syncIncomplete = YES;
599
            [pool drain];
600
            return;
601
        }
602
        NSLog(@"Sync::create local directory object: %@", filePath);
603
        BOOL directoryCreated = NO;
604
        if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
605
            NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
606
            error = nil;
607
            if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
608
                dispatch_async(dispatch_get_main_queue(), ^{
609
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
610
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
611
                                                              error:error];
612
                });
613
            } else {
614
                directoryCreated = YES;
615
                storedState.isDirectory = YES;
616
                [self saveLocalState];
617
            }
618
        } else {
619
            NSLog(@"Sync::local directory object exists: %@", filePath);
620
            directoryCreated = YES;
621
        }
622
        if (directoryCreated)
623
            dispatch_async(dispatch_get_main_queue(), ^{
624
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
625
                                                      message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
626
            });
627
    } else if (object.bytes == 0) {
628
        // Create local object with zero length
629
        if (!remoteObjectExists || remoteIsDirectory) {
630
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
631
            syncIncomplete = YES;
632
            [pool drain];
633
            return;
634
        }
635
        NSLog(@"Sync::create local zero length object: %@", filePath);
636
        BOOL fileCreated = NO;
637
        if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
638
            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
639
            // Create directory of the file, if it doesn't exist
640
            error = nil;
641
            if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
642
                dispatch_async(dispatch_get_main_queue(), ^{
643
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
644
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
645
                                                              error:error];
646
                });
647
            }
648
            error = nil;
649
            if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
650
                dispatch_async(dispatch_get_main_queue(), ^{
651
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
652
                                                            message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
653
                                                              error:error];
654
                });
655
            } else {
656
                fileCreated = YES;
657
                storedState.hash = object.objectHash;
658
                storedState.tmpFilePath = nil;
659
                [self saveLocalState];
660
            }
661
        } else {
662
            NSLog(@"Sync::local zero length object exists: %@", filePath);
663
            fileCreated = YES;
664
        }
665
        if (fileCreated)
666
            dispatch_async(dispatch_get_main_queue(), ^{
667
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
668
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
669
            });
670
    } else if (storedState.tmpFilePath == nil) {
671
        // Create new local object
672
        if (!remoteObjectExists || remoteIsDirectory) {
673
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
674
            syncIncomplete = YES;
675
            [pool drain];
676
            return;
677
        }
678
        // Create directory of the file, if it doesn't exist
679
        error = nil;
680
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
681
            dispatch_async(dispatch_get_main_queue(), ^{
682
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
683
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
684
                                                          error:error];
685
            });
686
        }
687
        // Check first if a local copy exists
688
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
689
            dispatch_async(dispatch_get_main_queue(), ^{
690
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
691
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
692
            });
693
        } else {
694
            [self increaseSyncOperationCount];
695
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
696
                                                                                                              object:object 
697
                                                                                                          blockIndex:0 
698
                                                                                                           blockSize:blockSize];
699
            objectRequest.delegate = self;
700
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
701
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
702
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
703
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
704
                                                                    totalBytes:object.bytes 
705
                                                                  currentBytes:0];
706
            dispatch_async(dispatch_get_main_queue(), ^{
707
                [activityFacility updateActivity:activity withMessage:activity.message];  
708
            });
709
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
710
                                      object, @"pithosObject", 
711
                                      [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks", 
712
                                      [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
713
                                      filePath, @"filePath", 
714
                                      activity, @"activity", 
715
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
716
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
717
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
718
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
719
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
720
                                      NSStringFromSelector(@selector(downloadObjectBlockFinished:)), @"didFinishSelector", 
721
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
722
                                      nil];
723
            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
724
                [activityFacility updateActivity:activity 
725
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
726
                                                  [[objectRequest.userInfo objectForKey:@"pithosObject"] name], 
727
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
728
                                      totalBytes:activity.totalBytes 
729
                                    currentBytes:(activity.currentBytes + size)];
730
            }];
731
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
732
        }
733
    } else {
734
        // Resume local object download
735
        if (!remoteObjectExists || remoteIsDirectory) {
736
            // Remote file object deleted or changed to a directory object in the meantime, mark the sync cycle as incomplete and skip
737
            syncIncomplete = YES;
738
            [pool drain];
739
            return;
740
        }
741
        // Create directory of the file, if it doesn't exist
742
        error = nil;
743
        if (![PithosUtilities safeCreateDirectory:fileDirectoryPath error:&error]) {
744
            dispatch_async(dispatch_get_main_queue(), ^{
745
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
746
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", fileDirectoryPath] 
747
                                                          error:error];
748
            });
749
        }
750
        // Check first if a local copy exists
751
        if ([self findLocalCopyForObjectWithHash:object.objectHash forFile:filePath]) {
752
            dispatch_async(dispatch_get_main_queue(), ^{
753
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
754
                                                      message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
755
            });
756
            // Delete incomplete temp download
757
            error = nil;
758
            storedState.tmpFilePath = nil;
759
        } else {
760
            [self increaseSyncOperationCount];
761
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName 
762
                                                                                                       objectName:object.name];
763
            objectRequest.delegate = self;
764
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
765
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
766
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
767
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
768
                                                                    totalBytes:object.bytes 
769
                                                                  currentBytes:0];
770
            dispatch_async(dispatch_get_main_queue(), ^{
771
                [activityFacility updateActivity:activity withMessage:activity.message];
772
            });
773
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
774
                                      object, @"pithosObject", 
775
                                      filePath, @"filePath", 
776
                                      activity, @"activity", 
777
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
778
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
779
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
780
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
781
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
782
                                      NSStringFromSelector(@selector(downloadObjectHashMapFinished:)), @"didFinishSelector", 
783
                                      NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
784
                                      nil];
785
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
786
        }
787
    }
788
    [pool drain];
789
}
790

    
791
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
792
                                  object:(ASIPithosObject *)object 
793
                           localFilePath:(NSString *)filePath {
794
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
795
    [self increaseSyncOperationCount];
796
    NSFileManager *fileManager = [NSFileManager defaultManager];
797
    BOOL isDirectory;
798
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
799
    if (currentState.isDirectory) {
800
        // Create remote directory object
801
        if (!fileExists || !isDirectory) {
802
            // Local directory object deleted or changed to a file in the meantime, mark the sync cycle as incomplete and skip
803
            syncIncomplete = YES;
804
            [self decreaseSyncOperationCount];
805
            [pool drain];
806
            return;
807
        }
808
        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
809
                                                                                                     objectName:object.name 
810
                                                                                                           eTag:nil 
811
                                                                                                    contentType:@"application/directory" 
812
                                                                                                contentEncoding:nil 
813
                                                                                             contentDisposition:nil 
814
                                                                                                       manifest:nil 
815
                                                                                                        sharing:nil 
816
                                                                                                       isPublic:ASIPithosObjectRequestPublicIgnore 
817
                                                                                                       metadata:nil 
818
                                                                                                           data:[NSData data]];
819
        objectRequest.delegate = self;
820
        objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
821
        objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
822
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
823
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
824
        dispatch_async(dispatch_get_main_queue(), ^{
825
            [activityFacility updateActivity:activity withMessage:activity.message];
826
        });
827
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
828
                                  object, @"pithosObject", 
829
                                  activity, @"activity", 
830
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage", 
831
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage", 
832
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage", 
833
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
834
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
835
                                  NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector", 
836
                                  NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
837
                                  nil];
838
        [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
839
    } else if (!currentState.exists) {
840
        // Delete remote object
841
        if (fileExists) {
842
            // Local object created in the meantime, just mark the sync cycle as incomplete, but do delete the server object
843
            syncIncomplete = YES;
844
        }
845
        NSString *safeName;
846
        if ([PithosUtilities isContentTypeDirectory:object.contentType])
847
            safeName = [PithosUtilities safeSubdirNameForContainerName:@"trash" 
848
                                                            subdirName:object.name];
849
        else
850
            safeName = [PithosUtilities safeObjectNameForContainerName:@"trash" 
851
                                                            objectName:object.name];
852
        if (safeName) {
853
            ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:containerName 
854
                                                                                             objectName:object.name 
855
                                                                               destinationContainerName:@"trash" 
856
                                                                                  destinationObjectName:safeName 
857
                                                                                          checkIfExists:NO];
858
            if (objectRequest) {
859
                objectRequest.delegate = self;
860
                objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
861
                objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
862
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
863
                                                                           message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
864
                dispatch_async(dispatch_get_main_queue(), ^{
865
                    [activityFacility updateActivity:activity withMessage:activity.message];
866
                });
867
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
868
                                          object, @"pithosObject", 
869
                                          activity, @"activity", 
870
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage", 
871
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage", 
872
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage", 
873
                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
874
                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
875
                                          NSStringFromSelector(@selector(moveObjectToTrashFinished:)), @"didFinishSelector", 
876
                                          NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
877
                                          nil];
878
                [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
879
            } else {
880
                syncIncomplete = YES;
881
                [self decreaseSyncOperationCount];
882
            }
883
        } else {
884
            syncIncomplete = YES;
885
            [self decreaseSyncOperationCount];
886
        }
887
    } else {
888
        // Upload file to remote object
889
        if (!fileExists || isDirectory) {
890
            // Local file object deleted or changed to a directory in the meantime, mark the sync cycle as incomplete and skip
891
            syncIncomplete = YES;
892
            [self decreaseSyncOperationCount];
893
            [pool drain];
894
            return;
895
        }
896
        NSError *error = nil;
897
        object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
898
        if (object.contentType == nil)
899
            object.contentType = @"application/octet-stream";
900
        if (error)
901
            NSLog(@"contentType detection error: %@", error);
902
        NSArray *hashes = nil;
903
        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
904
                                                                                              objectName:object.name 
905
                                                                                             contentType:object.contentType 
906
                                                                                               blockSize:blockSize 
907
                                                                                               blockHash:blockHash 
908
                                                                                                 forFile:filePath 
909
                                                                                           checkIfExists:NO 
910
                                                                                                  hashes:&hashes 
911
                                                                                          sharingAccount:nil];
912
        if (objectRequest) {
913
            objectRequest.delegate = self;
914
            objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
915
            objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
916
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
917
                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
918
                                                                    totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
919
                                                                  currentBytes:0];
920
            dispatch_async(dispatch_get_main_queue(), ^{
921
                [activityFacility updateActivity:activity withMessage:activity.message];
922
            });
923
            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
924
             [NSDictionary dictionaryWithObjectsAndKeys:
925
              object, @"pithosObject", 
926
              filePath, @"filePath", 
927
              hashes, @"hashes", 
928
              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
929
              activity, @"activity", 
930
              [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
931
              [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
932
              [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
933
              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
934
              [NSNumber numberWithUnsignedInteger:10], @"retries", 
935
              NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector", 
936
              NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector", 
937
              nil]];
938
            [networkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
939
        } else {
940
            syncIncomplete = YES;
941
            [self decreaseSyncOperationCount];
942
        }
943
    }
944
    [pool drain];
945
}
946

    
947
#pragma mark -
948
#pragma mark ASIHTTPRequestDelegate
949

    
950
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
951
    dispatch_async(queue, ^{
952
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) withObject:request];
953
    });
954
}
955

    
956
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
957
    dispatch_async(queue, ^{
958
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request];
959
    });
960
}
961

    
962
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
963
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
964
    NSLog(@"Sync::list request finished: %@", containerRequest.url);
965
    if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
966
        if (containerRequest.responseStatusCode == 200) {
967
            NSArray *someObjects = [containerRequest objects];
968
            if (objects == nil) {
969
                objects = [[NSMutableArray alloc] initWithArray:someObjects];
970
            } else {
971
                [objects addObjectsFromArray:someObjects];
972
            }
973
            if ([someObjects count] < 10000) {
974
                self.blockHash = [containerRequest blockHash];
975
                self.blockSize = [containerRequest blockSize];
976
                self.lastModified = [containerRequest lastModified];
977
                self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
978
                for (ASIPithosObject *object in objects) {
979
                    [remoteObjects setObject:object forKey:object.name];
980
                }
981
                [objects release];
982
                objects = nil;
983
            } else {
984
                // Do an additional request to fetch more objects
985
                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
986
                                                                                                                          limit:0 
987
                                                                                                                         marker:[[someObjects lastObject] name] 
988
                                                                                                                         prefix:nil 
989
                                                                                                                      delimiter:nil 
990
                                                                                                                           path:nil 
991
                                                                                                                           meta:nil 
992
                                                                                                                         shared:NO 
993
                                                                                                                          until:nil 
994
                                                                                                                ifModifiedSince:lastModified];
995
                newContainerRequest.delegate = self;
996
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
997
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
998
                newContainerRequest.userInfo = newContainerRequest.userInfo;
999
                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1000
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1001
                [pool drain];
1002
                return;
1003
            }
1004
        }
1005
        dispatch_async(dispatch_get_main_queue(), ^{
1006
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1007
                              withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1008
        });
1009
        NSFileManager *fileManager = [NSFileManager defaultManager];
1010
        NSError *error = nil;
1011
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
1012
        if (error) {
1013
            dispatch_async(dispatch_get_main_queue(), ^{
1014
                [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
1015
                                                        message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
1016
                                                          error:error];
1017
                [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
1018
            });
1019
            @synchronized(self) {
1020
                // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccessfully
1021
                syncOperationCount = 0;
1022
                if (newSyncRequested)
1023
                    [self sync];
1024
            }
1025
            [pool drain];
1026
            return;
1027
        }
1028
        self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
1029
        for (NSString *objectName in subPaths) {
1030
            if (![storedLocalObjectStates objectForKey:objectName]) {
1031
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1032
            }
1033
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1034
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
1035
                                                                                       blockHash:blockHash 
1036
                                                                                       blockSize:blockSize] 
1037
                                         forKey:filePath];
1038
        }
1039
        [self saveLocalState];
1040

    
1041
        for (NSString *objectName in remoteObjects) {
1042
            if (![storedLocalObjectStates objectForKey:objectName])
1043
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1044
        }
1045

    
1046
        for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1047
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1048
            if ([objectName hasSuffix:@"/"])
1049
                filePath = [filePath stringByAppendingString:@":"];
1050
            ASIPithosObject *object = [ASIPithosObject object];
1051
            object.name = objectName;
1052
            NSLog(@"Sync::object name: %@", objectName);
1053
            
1054
            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
1055
            PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1056
            if (!currentLocalObjectState)
1057
                currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
1058
                                                                                 blockHash:blockHash 
1059
                                                                                 blockSize:blockSize];
1060
            if (currentLocalObjectState.isDirectory)
1061
                object.contentType = @"application/directory";
1062
            
1063
            PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1064
            ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
1065
            if (remoteObject) {
1066
                if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1067
                    remoteObjectState.isDirectory = YES;
1068
                    object.contentType = @"application/directory";
1069
                } else {
1070
                    remoteObjectState.hash = remoteObject.objectHash;
1071
                }
1072
            }
1073

    
1074
            BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
1075
            BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
1076
            NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
1077
            if (!localStateHasChanged) {
1078
                // Local state hasn't changed
1079
                if (serverStateHasChanged) {
1080
                    // Server state has changed
1081
                    // Update local state to match that of the server 
1082
                    object.bytes = remoteObject.bytes;
1083
                    object.version = remoteObject.version;
1084
                    object.contentType = remoteObject.contentType;
1085
                    object.objectHash = remoteObject.objectHash;
1086
                    [self updateLocalStateWithObject:object localFilePath:filePath];
1087
                } else if (!remoteObject && !currentLocalObjectState.exists) {
1088
                    // Server state hasn't changed
1089
                    // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
1090
                    [storedLocalObjectStates removeObjectForKey:objectName];
1091
                    [self saveLocalState];
1092
                }
1093
            } else {
1094
                // Local state has changed
1095
                if (!serverStateHasChanged) {
1096
                    // Server state hasn't changed
1097
                    [self updateServerStateWithCurrentState:currentLocalObjectState 
1098
                                                     object:object 
1099
                                              localFilePath:filePath];
1100
                } else {
1101
                    // Server state has also changed
1102
                    if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
1103
                        // Both did the same change (directory)
1104
                        storedLocalObjectState.isDirectory = YES;
1105
                        [self saveLocalState];
1106
                    } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
1107
                        // Both did the same change (object edit or delete)
1108
                        if (!remoteObjectState.exists)
1109
                            [storedLocalObjectStates removeObjectForKey:object.name];
1110
                        else
1111
                            storedLocalObjectState.hash = remoteObjectState.hash;
1112
                        [self saveLocalState];
1113
                    } else {
1114
                        // Conflict, we ask the user which change to keep
1115
                        NSString *informativeText;
1116
                        NSString *firstButtonText;
1117
                        NSString *secondButtonText;
1118
                        
1119
                        if (!remoteObjectState.exists) {
1120
                            // Remote object has been deleted
1121
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
1122
                            firstButtonText = @"Delete local file";
1123
                            secondButtonText = @"Upload file to server";
1124
                        } else if (!currentLocalObjectState.exists) {
1125
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
1126
                            firstButtonText = @"Download file from server";
1127
                            secondButtonText = @"Delete file on server";
1128
                        } else {
1129
                            informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
1130
                            firstButtonText = @"Keep server version";
1131
                            secondButtonText = @"Keep local version";
1132
                        }
1133
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1134
                        [alert setMessageText:@"Conflict"];
1135
                        [alert setInformativeText:informativeText];
1136
                        [alert addButtonWithTitle:firstButtonText];
1137
                        [alert addButtonWithTitle:secondButtonText];
1138
                        [alert addButtonWithTitle:@"Do nothing"];
1139
                        NSInteger choice = [alert runModal];
1140
                        if (choice == NSAlertFirstButtonReturn) {
1141
                            object.bytes = remoteObject.bytes;
1142
                            object.version = remoteObject.version;
1143
                            object.contentType = remoteObject.contentType;
1144
                            object.objectHash = remoteObject.objectHash;
1145
                            [self updateLocalStateWithObject:object localFilePath:filePath];
1146
                        } if (choice == NSAlertSecondButtonReturn) {
1147
                            [self updateServerStateWithCurrentState:currentLocalObjectState 
1148
                                                             object:object 
1149
                                                      localFilePath:filePath];
1150
                        }
1151
                    }
1152
                }
1153
            }
1154
        }
1155
        @synchronized(self) {
1156
            [self decreaseSyncOperationCount];
1157
            if (newSyncRequested && !syncOperationCount)
1158
                [self sync];
1159
        }
1160
    } else {
1161
        NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1162
        if (retries > 0) {
1163
            ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1164
            [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1165
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1166
        } else {
1167
            dispatch_async(dispatch_get_main_queue(), ^{
1168
                [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1169
                                  withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1170
            });
1171
            @synchronized(self) {
1172
                // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1173
                syncOperationCount = 0;
1174
                if (newSyncRequested)
1175
                    [self sync];
1176
            }
1177
        }
1178
    }
1179
    [pool drain];
1180
}
1181

    
1182
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
1183
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1184
    if ([containerRequest isCancelled]) {
1185
        dispatch_async(dispatch_get_main_queue(), ^{
1186
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1187
                              withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1188
        });
1189
        [objects release];
1190
        objects = nil;
1191
        @synchronized(self) {
1192
            syncOperationCount = 0;
1193
        }
1194
        [pool drain];
1195
        return;
1196
    }
1197
    // If the server listing fails, the sync should start over, so just retrying is enough
1198
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1199
    if (retries > 0) {
1200
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1201
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1202
        [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1203
    } else {
1204
        dispatch_async(dispatch_get_main_queue(), ^{
1205
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
1206
                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
1207
        });
1208
        [objects release];
1209
        objects = nil;
1210
        @synchronized(self) {
1211
            // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
1212
            syncOperationCount = 0;
1213
            if (newSyncRequested)
1214
                [self sync];
1215
        }
1216
    }
1217
    [pool drain];
1218
}
1219

    
1220
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1221
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1222
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
1223
    if (objectRequest.responseStatusCode == 206) {
1224
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1225
        NSFileManager *fileManager = [NSFileManager defaultManager];
1226
        NSError *error;
1227
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1228
        
1229
        NSString *downloadsDirPath = self.tempDownloadsDirPath;
1230
        if (!downloadsDirPath) {
1231
            dispatch_async(dispatch_get_main_queue(), ^{
1232
                [activityFacility endActivity:activity 
1233
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1234
            });
1235
            @synchronized(self) {
1236
                syncIncomplete = YES;
1237
                [self decreaseSyncOperationCount];
1238
                if (newSyncRequested && !syncOperationCount)
1239
                    [self sync];
1240
            }
1241
            [pool drain];
1242
            return;
1243
        }
1244
        
1245
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1246
        if ((storedState.tmpFilePath == nil) || ![fileManager fileExistsAtPath:storedState.tmpFilePath]) {
1247
            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1248
            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1249
            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1250
            strcpy(tempFileNameCString, tempFileTemplateCString);
1251
            int fileDescriptor = mkstemp(tempFileNameCString);
1252
            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1253
            free(tempFileNameCString);
1254
            if (fileDescriptor == -1) {
1255
                dispatch_async(dispatch_get_main_queue(), ^{
1256
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1257
                                                            message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath] 
1258
                                                              error:nil];
1259
                    [activityFacility endActivity:activity 
1260
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1261
                });
1262
                @synchronized(self) {
1263
                    syncIncomplete = YES;
1264
                    [self decreaseSyncOperationCount];
1265
                    if (newSyncRequested && !syncOperationCount)
1266
                        [self sync];
1267
                }
1268
                [pool drain];
1269
                return;
1270
            }
1271
            close(fileDescriptor);
1272
            storedState.tmpFilePath = tempFilePath;
1273
            [self saveLocalState];
1274
        }
1275
        
1276

    
1277
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1278
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1279
        [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1280
        [tempFileHandle writeData:[objectRequest responseData]];
1281
        [tempFileHandle closeFile];
1282

    
1283
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1284
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1285
        if (missingBlockIndex == NSNotFound) {
1286
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1287
            NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1288
            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1289
                dispatch_async(dispatch_get_main_queue(), ^{
1290
                    [activityFacility endActivity:activity 
1291
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1292
                });
1293
                @synchronized(self) {
1294
                    syncIncomplete = YES;
1295
                    [self decreaseSyncOperationCount];
1296
                    if (newSyncRequested && !syncOperationCount)
1297
                        [self sync];
1298
                }
1299
                [pool drain];
1300
                return;
1301
            } else if (![fileManager fileExistsAtPath:dirPath]) {
1302
                // File doesn't exist but also the containing directory doesn't exist
1303
                // In most cases this should have been resolved as an update of the corresponding local object,
1304
                // but it never hurts to check
1305
                error = nil;
1306
                [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1307
                if (error != nil) {
1308
                    dispatch_async(dispatch_get_main_queue(), ^{
1309
                        [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1310
                                                                message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1311
                                                                  error:error];
1312
                        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1313
                                          withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1314
                    });
1315
                    @synchronized(self) {
1316
                        syncIncomplete = YES;
1317
                        [self decreaseSyncOperationCount];
1318
                        if (newSyncRequested && !syncOperationCount)
1319
                            [self sync];
1320
                    }
1321
                    [pool drain];
1322
                    return;
1323
                }
1324
            }
1325
            // Move file from tmp download
1326
            error = nil;
1327
            [fileManager moveItemAtPath:storedState.tmpFilePath toPath:filePath error:&error];
1328
            if (error != nil) {
1329
                dispatch_async(dispatch_get_main_queue(), ^{
1330
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1331
                                                            message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.tmpFilePath, filePath] 
1332
                                                              error:error];
1333
                    [activityFacility endActivity:activity 
1334
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1335
                });
1336
                @synchronized(self) {
1337
                    syncIncomplete = YES;
1338
                    [self decreaseSyncOperationCount];
1339
                    if (newSyncRequested && !syncOperationCount)
1340
                        [self sync];
1341
                }
1342
                [pool drain];
1343
                return;
1344
            }
1345
            dispatch_async(dispatch_get_main_queue(), ^{
1346
                [activityFacility endActivity:activity 
1347
                                  withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1348
                                   totalBytes:activity.totalBytes 
1349
                                 currentBytes:activity.totalBytes];
1350
            });
1351

    
1352
            storedState.hash = object.objectHash;
1353
            storedState.tmpFilePath = nil;
1354
            [self saveLocalState];
1355
            
1356
            @synchronized(self) {
1357
                [self decreaseSyncOperationCount];
1358
                if (newSyncRequested && !syncOperationCount)
1359
                    [self sync];
1360
            }
1361
            [pool drain];
1362
            return;
1363
        } else {
1364
            if (newSyncRequested) {
1365
                dispatch_async(dispatch_get_main_queue(), ^{
1366
                    [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1367
                                      withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1368
                });
1369
                @synchronized(self) {
1370
                    syncIncomplete = YES;
1371
                    [self decreaseSyncOperationCount];
1372
                    if (!syncOperationCount)
1373
                        [self sync];
1374
                }
1375
                [pool drain];
1376
                return;
1377
            } else {
1378
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1379
                                                                                                                     object:object 
1380
                                                                                                                 blockIndex:missingBlockIndex 
1381
                                                                                                                  blockSize:blockSize];
1382
                newObjectRequest.delegate = self;
1383
                newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1384
                newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1385
                newObjectRequest.userInfo = objectRequest.userInfo;
1386
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1387
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1388
                [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1389
                    [activityFacility updateActivity:activity 
1390
                                         withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1391
                                                      object.name, 
1392
                                                      (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1393
                                          totalBytes:activity.totalBytes 
1394
                                        currentBytes:(activity.currentBytes + size)];
1395
                }];
1396
                [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1397
            }
1398
        }
1399
    } else if (objectRequest.responseStatusCode == 412) {
1400
        // The object has changed on the server
1401
        dispatch_async(dispatch_get_main_queue(), ^{
1402
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1403
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1404
        });
1405
        @synchronized(self) {
1406
            syncIncomplete = YES;
1407
            [self decreaseSyncOperationCount];
1408
            if (newSyncRequested && !syncOperationCount)
1409
                [self sync];
1410
        }
1411
    } else {
1412
        [self requestFailed:objectRequest];
1413
    }
1414
    [pool drain];
1415
}
1416

    
1417
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1418
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1419
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1420
    if (objectRequest.responseStatusCode == 200) {
1421
        if (newSyncRequested) {
1422
            dispatch_async(dispatch_get_main_queue(), ^{
1423
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1424
                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1425
            });
1426
            @synchronized(self) {
1427
                syncIncomplete = YES;
1428
                [self decreaseSyncOperationCount];
1429
                if (!syncOperationCount)
1430
                    [self sync];
1431
            }
1432
            [pool drain];
1433
            return;
1434
        } else {
1435
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1436
            PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1437
            if ([PithosUtilities bytesOfFile:storedState.tmpFilePath] > object.bytes)
1438
                [[NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath] truncateFileAtOffset:object.bytes];
1439
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1440
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.tmpFilePath 
1441
                                                                    blockSize:blockSize 
1442
                                                                    blockHash:blockHash 
1443
                                                                   withHashes:[objectRequest hashes]];
1444
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1445
            dispatch_async(dispatch_get_main_queue(), ^{
1446
                [activityFacility endActivity:activity 
1447
                                  withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1448
                                               object.name, 
1449
                                               (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1450
                                   totalBytes:activity.totalBytes 
1451
                                 currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1452
            });
1453

    
1454
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1455
                                                                                                                 object:object 
1456
                                                                                                             blockIndex:missingBlockIndex 
1457
                                                                                                              blockSize:blockSize];
1458
            newObjectRequest.delegate = self;
1459
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1460
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1461
            newObjectRequest.userInfo = objectRequest.userInfo;
1462
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1463
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1464
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1465
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1466
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1467
                [activityFacility updateActivity:activity 
1468
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1469
                                                  object.name, 
1470
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1471
                                      totalBytes:activity.totalBytes 
1472
                                    currentBytes:(activity.currentBytes + size)];
1473
            }];
1474
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1475
        }
1476
    } else {
1477
        [self requestFailed:objectRequest];
1478
    }
1479
    [pool drain];
1480
}
1481

    
1482
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1483
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1484
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1485
    if (objectRequest.responseStatusCode == 201) {
1486
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1487
        storedState.isDirectory = YES;
1488
        [self saveLocalState];
1489
        dispatch_async(dispatch_get_main_queue(), ^{
1490
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1491
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1492
        });
1493
        @synchronized(self) {
1494
            [self decreaseSyncOperationCount];
1495
            if (newSyncRequested && !syncOperationCount)
1496
                [self sync];
1497
        }
1498
    } else {
1499
        [self requestFailed:objectRequest];
1500
    }
1501
    [pool drain];
1502
}
1503

    
1504
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1505
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1506
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1507
    if (objectRequest.responseStatusCode == 201) {
1508
        [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1509
        [self saveLocalState];
1510
        dispatch_async(dispatch_get_main_queue(), ^{
1511
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1512
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1513
        });
1514
        @synchronized(self) {
1515
            [self decreaseSyncOperationCount];
1516
            if (newSyncRequested && !syncOperationCount)
1517
                [self sync];
1518
        }
1519
    } else {
1520
        [self requestFailed:objectRequest];
1521
    }
1522
    [pool drain];
1523
}
1524

    
1525
- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1526
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1527
    NSLog(@"Sync::upload using hashmap finished: %@", objectRequest.url);
1528
    ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1529
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1530
    PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1531
    NSUInteger totalBytes = activity.totalBytes;
1532
    NSUInteger currentBytes = activity.currentBytes;
1533
    if (objectRequest.responseStatusCode == 201) {
1534
        NSLog(@"Sync::object created: %@", objectRequest.url);
1535
        storedState.hash = [objectRequest objectHash];
1536
        [self saveLocalState];
1537
        dispatch_async(dispatch_get_main_queue(), ^{
1538
            [activityFacility endActivity:activity 
1539
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1540
                               totalBytes:totalBytes 
1541
                             currentBytes:totalBytes];
1542
        });
1543
        @synchronized(self) {
1544
            [self decreaseSyncOperationCount];
1545
            if (newSyncRequested && !syncOperationCount)
1546
                [self sync];
1547
        }
1548
    } else if (objectRequest.responseStatusCode == 409) {
1549
        if (newSyncRequested) {
1550
            dispatch_async(dispatch_get_main_queue(), ^{
1551
                [activityFacility endActivity:activity 
1552
                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1553
            });
1554
            @synchronized(self) {
1555
                syncIncomplete = YES;
1556
                [self decreaseSyncOperationCount];
1557
                if (!syncOperationCount)
1558
                    [self sync];
1559
            }
1560
            [pool drain];
1561
            return;
1562
        } else {
1563
            NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1564
            if (iteration == 0) {
1565
                NSLog(@"Sync::upload iteration limit reached: %@", objectRequest.url);
1566
                dispatch_async(dispatch_get_main_queue(), ^{
1567
                    [activityFacility endActivity:activity 
1568
                                      withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1569
                });
1570
                syncIncomplete = YES;
1571
                [self decreaseSyncOperationCount];
1572
                [pool drain];
1573
                return;
1574
            }
1575
            NSLog(@"Sync::object is missing hashes: %@", objectRequest.url);
1576
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1577
                                                      withMissingHashesResponse:[objectRequest responseString]];
1578
            if (totalBytes >= [missingBlocks count]*blockSize)
1579
                currentBytes = totalBytes - [missingBlocks count]*blockSize;
1580
            dispatch_async(dispatch_get_main_queue(), ^{
1581
                [activityFacility updateActivity:activity 
1582
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1583
                                      totalBytes:totalBytes 
1584
                                    currentBytes:currentBytes];
1585
            });
1586
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1587
            __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName 
1588
                                                                                                                        blockSize:blockSize 
1589
                                                                                                                          forFile:[objectRequest.userInfo objectForKey:@"filePath"] 
1590
                                                                                                                missingBlockIndex:missingBlockIndex 
1591
                                                                                                                   sharingAccount:nil];
1592
            newContainerRequest.delegate = self;
1593
            newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1594
            newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1595
            newContainerRequest.userInfo = objectRequest.userInfo;
1596
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1597
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1598
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1599
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1600
            [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1601
            [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1602
                [activityFacility updateActivity:activity 
1603
                                     withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1604
                                      totalBytes:activity.totalBytes 
1605
                                    currentBytes:(activity.currentBytes + size)];
1606
            }];
1607
            [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1608
        }
1609
    } else {
1610
        [self requestFailed:objectRequest];
1611
    }
1612
    [pool drain];
1613
}
1614

    
1615
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1616
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1617
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1618
    if (containerRequest.responseStatusCode == 202) {
1619
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1620
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1621
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1622
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1623
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1624
        if (missingBlockIndex == NSNotFound) {
1625
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1626
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
1627
                                                                                                     objectName:object.name 
1628
                                                                                                    contentType:object.contentType 
1629
                                                                                                      blockSize:blockSize 
1630
                                                                                                      blockHash:blockHash
1631
                                                                                                        forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1632
                                                                                                  checkIfExists:NO 
1633
                                                                                                         hashes:&hashes 
1634
                                                                                                 sharingAccount:nil];
1635
            newObjectRequest.delegate = self;
1636
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1637
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1638
            newObjectRequest.userInfo = containerRequest.userInfo;
1639
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1640
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1641
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1642
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1643
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1644
        } else {
1645
            if (newSyncRequested) {
1646
                dispatch_async(dispatch_get_main_queue(), ^{
1647
                    [activityFacility endActivity:activity 
1648
                                      withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1649
                });
1650
                @synchronized(self) {
1651
                    syncIncomplete = YES;
1652
                    [self decreaseSyncOperationCount];
1653
                    if (!syncOperationCount)
1654
                        [self sync];
1655
                }
1656
                [pool drain];
1657
                return;
1658
            } else {
1659
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1660
                                                                                                                            blockSize:blockSize
1661
                                                                                                                              forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1662
                                                                                                                    missingBlockIndex:missingBlockIndex 
1663
                                                                                                                       sharingAccount:nil];
1664
                newContainerRequest.delegate = self;
1665
                newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1666
                newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1667
                newContainerRequest.userInfo = containerRequest.userInfo;
1668
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1669
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1670
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1671
                    [activityFacility updateActivity:activity 
1672
                                         withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1673
                                          totalBytes:activity.totalBytes 
1674
                                        currentBytes:(activity.currentBytes + size)];
1675
                }];
1676
                [networkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1677
            }
1678
        }
1679
    } else {
1680
        [self requestFailed:containerRequest];
1681
    }
1682
    [pool drain];
1683
}
1684

    
1685
- (void)requestFailed:(ASIPithosRequest *)request {
1686
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1687
    if ([request isCancelled]) {
1688
        dispatch_async(dispatch_get_main_queue(), ^{
1689
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1690
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1691
        });
1692
        syncIncomplete = YES;
1693
        [self decreaseSyncOperationCount];
1694
        [pool drain];
1695
        return;
1696
    }
1697
    if (newSyncRequested) {
1698
        dispatch_async(dispatch_get_main_queue(), ^{
1699
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1700
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1701
        });
1702
        @synchronized(self) {
1703
            syncIncomplete = YES;
1704
            [self decreaseSyncOperationCount];
1705
            if (!syncOperationCount)
1706
                [self sync];
1707
        }
1708
        [pool drain];
1709
        return;
1710
    }
1711
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1712
    if (retries > 0) {
1713
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1714
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1715
        [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1716
    } else {
1717
        dispatch_async(dispatch_get_main_queue(), ^{
1718
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1719
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1720
        });
1721
        syncIncomplete = YES;
1722
        [self decreaseSyncOperationCount];
1723
    }
1724
    [pool drain];
1725
}
1726

    
1727

    
1728
@end