Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ 919cb043

History | View | Annotate | Download (103.6 kB)

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

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

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

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

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

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

    
66
@end
67

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

    
72
#pragma mark -
73
#pragma Object Lifecycle
74

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

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

    
171
#pragma mark -
172
#pragma mark Observers
173

    
174
- (void)applicationWillTerminate:(NSNotification *)notification {
175
    [self saveLocalState];
176
}
177

    
178
#pragma mark -
179
#pragma mark Properties
180

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

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

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

    
263
#pragma mark -
264
#pragma mark Sync
265

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

    
276
- (void)increaseSyncOperationCount {
277
    @synchronized(self) {
278
        syncOperationCount++;
279
    }
280
}
281

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

    
292
                });
293
            }
294
            [self emptyTempTrash];
295
        }
296
    }
297
}
298

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

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

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

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

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

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

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

    
956
#pragma mark -
957
#pragma mark ASIHTTPRequestDelegate
958

    
959
- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
960
    dispatch_async(queue, ^{
961
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"]) withObject:request];
962
    });
963
}
964

    
965
- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
966
    dispatch_async(queue, ^{
967
        [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"]) withObject:request];
968
    });
969
}
970

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

    
1051
        for (NSString *objectName in remoteObjects) {
1052
            if (![storedLocalObjectStates objectForKey:objectName])
1053
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
1054
        }
1055

    
1056
        for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
1057
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
1058
            if ([objectName hasSuffix:@"/"])
1059
                filePath = [filePath stringByAppendingString:@":"];
1060
            ASIPithosObject *object = [ASIPithosObject object];
1061
            object.name = objectName;
1062
            NSLog(@"Sync::object name: %@", objectName);
1063
            
1064
            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
1065
            PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
1066
            if (!currentLocalObjectState)
1067
                currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
1068
                                                                                 blockHash:blockHash 
1069
                                                                                 blockSize:blockSize];
1070
            if (currentLocalObjectState.isDirectory)
1071
                object.contentType = @"application/directory";
1072
            
1073
            PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
1074
            ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
1075
            if (remoteObject) {
1076
                if ([PithosUtilities isContentTypeDirectory:remoteObject.contentType]) {
1077
                    remoteObjectState.isDirectory = YES;
1078
                    object.contentType = @"application/directory";
1079
                } else {
1080
                    remoteObjectState.hash = remoteObject.objectHash;
1081
                }
1082
            }
1083

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

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

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

    
1287
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1288
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.tmpFilePath];
1289
        [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1290
        [tempFileHandle writeData:[objectRequest responseData]];
1291
        [tempFileHandle closeFile];
1292

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

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

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

    
1465
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithPithos:pithos 
1466
                                                                                                   containerName:containerName 
1467
                                                                                                          object:object 
1468
                                                                                                      blockIndex:missingBlockIndex 
1469
                                                                                                       blockSize:blockSize];
1470
            newObjectRequest.delegate = self;
1471
            newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1472
            newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1473
            newObjectRequest.userInfo = objectRequest.userInfo;
1474
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1475
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1476
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1477
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(downloadObjectBlockFinished:)) forKey:@"didFinishSelector"];
1478
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1479
                [activityFacility updateActivity:activity 
1480
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1481
                                                  object.name, 
1482
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1483
                                      totalBytes:activity.totalBytes 
1484
                                    currentBytes:(activity.currentBytes + size)];
1485
            }];
1486
            [networkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1487
        }
1488
    } else {
1489
        [self requestFailed:objectRequest];
1490
    }
1491
    [pool drain];
1492
}
1493

    
1494
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1495
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1496
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1497
    if (objectRequest.responseStatusCode == 201) {
1498
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1499
        storedState.isDirectory = YES;
1500
        [self saveLocalState];
1501
        dispatch_async(dispatch_get_main_queue(), ^{
1502
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1503
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1504
        });
1505
        @synchronized(self) {
1506
            [self decreaseSyncOperationCount];
1507
            if (newSyncRequested && !syncOperationCount)
1508
                [self sync];
1509
        }
1510
    } else {
1511
        [self requestFailed:objectRequest];
1512
    }
1513
    [pool drain];
1514
}
1515

    
1516
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1517
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1518
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1519
    if (objectRequest.responseStatusCode == 201) {
1520
        [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1521
        [self saveLocalState];
1522
        dispatch_async(dispatch_get_main_queue(), ^{
1523
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1524
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1525
        });
1526
        @synchronized(self) {
1527
            [self decreaseSyncOperationCount];
1528
            if (newSyncRequested && !syncOperationCount)
1529
                [self sync];
1530
        }
1531
    } else {
1532
        [self requestFailed:objectRequest];
1533
    }
1534
    [pool drain];
1535
}
1536

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

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

    
1700
- (void)requestFailed:(ASIPithosRequest *)request {
1701
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1702
    if ([request isCancelled]) {
1703
        dispatch_async(dispatch_get_main_queue(), ^{
1704
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1705
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1706
        });
1707
        syncIncomplete = YES;
1708
        [self decreaseSyncOperationCount];
1709
        [pool drain];
1710
        return;
1711
    }
1712
    if (newSyncRequested) {
1713
        dispatch_async(dispatch_get_main_queue(), ^{
1714
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1715
                              withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1716
        });
1717
        @synchronized(self) {
1718
            syncIncomplete = YES;
1719
            [self decreaseSyncOperationCount];
1720
            if (!syncOperationCount)
1721
                [self sync];
1722
        }
1723
        [pool drain];
1724
        return;
1725
    }
1726
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1727
    if (retries > 0) {
1728
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1729
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1730
        [networkQueue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1731
    } else {
1732
        dispatch_async(dispatch_get_main_queue(), ^{
1733
            [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1734
                              withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1735
        });
1736
        syncIncomplete = YES;
1737
        [self decreaseSyncOperationCount];
1738
    }
1739
    [pool drain];
1740
}
1741

    
1742

    
1743
@end