Statistics
| Branch: | Tag: | Revision:

root / pithos-macos / PithosSyncDaemon.m @ 2943d6d4

History | View | Annotate | Download (86.8 kB)

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

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

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

    
51
@interface PithosSyncDaemon (Private)
52
- (NSString *)pithosStateFilePath;
53
- (void)saveLocalState;
54

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

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

    
65
- (void)increaseSyncOperationCount;
66
- (void)decreaseSyncOperationCount;
67

    
68
@end
69

    
70
@implementation PithosSyncDaemon
71
@synthesize blockHash, blockSize, lastModified, lastCompletedSync, remoteObjects, storedLocalObjectStates, currentLocalObjectStates;
72
@synthesize containerDirectoryPath, pithosStateFilePath, tempDownloadsDirPath, tempTrashDirPath;
73

    
74
#pragma mark -
75
#pragma Object Lifecycle
76

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

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

    
138
#pragma mark -
139
#pragma mark Observers
140

    
141
- (void)applicationWillTerminate:(NSNotification *)notification {
142
    [self saveLocalState];
143
}
144

    
145
#pragma mark -
146
#pragma mark Properties
147

    
148
- (NSString *)pithosStateFilePath {
149
    if (!pithosStateFilePath) {
150
        pithosStateFilePath = [[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:DATA_MODEL_FILE] retain];
151
    }
152
    return [pithosStateFilePath copy];
153
}
154

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

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

    
223
#pragma mark -
224
#pragma mark Sync
225

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

    
234
- (void)increaseSyncOperationCount {
235
    @synchronized(self) {
236
        syncOperationCount++;
237
    }
238
}
239

    
240
- (void)decreaseSyncOperationCount {
241
    @synchronized(self) {
242
        syncOperationCount--;
243
        if (!syncOperationCount) {
244
            if (!syncIncomplete) {
245
                self.lastCompletedSync = [NSDate date];
246
                [activityFacility startAndEndActivityWithType:PithosActivityOther 
247
                                                      message:[NSString stringWithFormat:@"Sync: Completed %@", lastCompletedSync]];
248
            }
249
            [self emptyTempTrash];
250
        }
251
    }
252
}
253

    
254
- (void)sync {
255
    @synchronized(self) {
256
        if (syncOperationCount) {
257
            // If at least one operation is running return
258
            newSyncRequested = YES;
259
            return;
260
        } else {
261
            // The first operation is the server listing
262
            syncOperationCount = 1;
263
            newSyncRequested = NO;
264
            syncIncomplete = NO;
265
        }
266
    }
267

    
268
    NSFileManager *fileManager = [NSFileManager defaultManager];
269
    BOOL isDirectory;
270
    NSError *error = nil;
271
    if (![fileManager fileExistsAtPath:containerDirectoryPath isDirectory:&isDirectory]) {
272
        if (![fileManager createDirectoryAtPath:containerDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error] || 
273
            error) {
274
            [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
275
                                                    message:[NSString stringWithFormat:@"Cannot create local sync directory at '%@'", containerDirectoryPath] 
276
                                                      error:error];
277
            @synchronized(self) {
278
                syncOperationCount = 0;
279
            }
280
            return;
281
        }
282
    } else if (!isDirectory) {
283
        [PithosUtilities fileActionFailedAlertWithTitle:@"Local Sync Directory Error" 
284
                                                message:[NSString stringWithFormat:@"File already exists at the local sync directory path at '%@'", containerDirectoryPath] 
285
                                                  error:nil];
286
        @synchronized(self) {
287
            syncOperationCount = 0;
288
        }
289
        return;
290
    }
291
    
292
    ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
293
                                                                                                           limit:0 
294
                                                                                                          marker:nil 
295
                                                                                                          prefix:nil 
296
                                                                                                       delimiter:nil 
297
                                                                                                            path:nil 
298
                                                                                                            meta:nil 
299
                                                                                                          shared:NO 
300
                                                                                                           until:nil 
301
                                                                                                 ifModifiedSince:lastModified];
302
    containerRequest.delegate = self;
303
    containerRequest.didFinishSelector = @selector(listRequestFinished:);
304
    containerRequest.didFailSelector = @selector(listRequestFailed:);
305
    PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityOther 
306
                                                               message:@"Sync: Getting server listing"];
307
    containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
308
                                 activity, @"activity", 
309
                                 @"Sync: Getting server listing (stopped)", @"stoppedActivityMessage", 
310
                                 @"Sync: Getting server listing (failed)", @"failedActivityMessage", 
311
                                 @"Sync: Getting server listing (finished)", @"finishedActivityMessage", 
312
                                 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
313
                                 [NSNumber numberWithUnsignedInteger:10], @"retries", 
314
                                 nil];
315
    [queue addOperation:[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh]];
316
}
317

    
318
- (void)emptyTempTrash {
319
    NSString *trashDirPath = self.tempTrashDirPath;
320
    if (tempTrashDirPath) {
321
        NSFileManager *fileManager = [NSFileManager defaultManager];
322
        NSError *error = nil;
323
//        NSArray *subPaths = [[NSFileManager defaultManager] subpathsOfDirectoryAtPath:trashDirPath error:&error];
324
        NSArray *subPaths = [fileManager contentsOfDirectoryAtPath:trashDirPath error:&error];
325
        if (error) {
326
            [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
327
                                                    message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
328
                                                      error:error];
329
            return;
330
        }
331
        if ([subPaths count]) {
332
//            NSMutableArray *subURLs = [NSMutableArray arrayWithCapacity:[subPaths count]];
333
//            for (NSString *subPath in subPaths) {
334
//                [subURLs addObject:[NSURL fileURLWithPath:[trashDirPath stringByAppendingPathComponent:subPath]]];
335
//            }
336
//            syncOperationCount = 1;
337
//            [[NSWorkspace sharedWorkspace] recycleURLs:subURLs completionHandler:^(NSDictionary *newURLs, NSError *error) {
338
//                if (error) {
339
//                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move to Trash Error" 
340
//                                                            message:@"Cannot move files to Trash" 
341
//                                                              error:error];
342
//                }
343
//                syncOperationCount = 0;
344
//            }];
345
            for (NSString *subPath in subPaths) {
346
                error = nil;
347
                if (![fileManager removeItemAtPath:subPath error:&error] || error)
348
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
349
                                                            message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subPath] 
350
                                                              error:error];
351
            }
352
        }
353
    }    
354
}
355

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

    
444
- (BOOL)findLocalCopyForObjectWithHash:(NSString *)hash forFile:(NSString *)filePath {
445
    NSUInteger hashLength = [hash length];
446
    if ((hashLength != 32) && (hashLength != 64))
447
        return NO;
448
    PithosLocalObjectState *localState;
449
    NSFileManager *fileManager = [NSFileManager defaultManager];
450
    BOOL isDirectory;
451
    NSError *error = nil;
452
    for (NSString *localFilePath in [currentLocalObjectStates allKeys]) {
453
        localState = [currentLocalObjectStates objectForKey:localFilePath];
454
        if (!localState.isDirectory && ([hash isEqualToString:localState.md5] || [hash isEqualToString:localState.hashMapHash]) && 
455
            [fileManager fileExistsAtPath:localFilePath isDirectory:&isDirectory] && !isDirectory) {
456
            if ([localFilePath hasPrefix:containerDirectoryPath]) {
457
                if (![fileManager copyItemAtPath:localFilePath toPath:filePath error:&error] || error) {
458
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Copy File Error" 
459
                                                            message:[NSString stringWithFormat:@"Cannot copy file at '%@' to '%@'", 
460
                                                                     localFilePath, filePath] 
461
                                                              error:error];
462
                } else {
463
                    return YES;
464
                }            
465
            } else if (self.tempTrashDirPath && [localFilePath hasPrefix:self.tempTrashDirPath]) {
466
                if (![fileManager moveItemAtPath:localFilePath toPath:filePath error:&error] || error) {
467
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
468
                                                            message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", 
469
                                                                     localFilePath, filePath] 
470
                                                              error:error];
471
                } else {
472
                    localState.filePath = filePath;
473
                    [currentLocalObjectStates setObject:localState forKey:filePath];
474
                    [currentLocalObjectStates removeObjectForKey:localFilePath];
475
                    return YES;
476
                }
477
            }
478
        }
479
    }
480
    return NO;
481
}
482

    
483
- (void)updateLocalStateWithObject:(ASIPithosObject *)object
484
                     localFilePath:(NSString *)filePath {
485
    NSFileManager *fileManager = [NSFileManager defaultManager];
486
    NSError *error;
487
    BOOL isDirectory;
488
    BOOL fileExists = [fileManager fileExistsAtPath:filePath isDirectory:&isDirectory];
489
    PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
490
    if (!object || !object.hash) {
491
        // Delete local object
492
        NSLog(@"Sync::delete local object: %@", filePath);
493
        if (!fileExists || [self moveToTempTrashFile:filePath]) {
494
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
495
                                                  message:[NSString stringWithFormat:@"Sync: Deleting '%@' locally (finished)", object.name]];
496
            [storedLocalObjectStates removeObjectForKey:object.name];
497
            [self saveLocalState];
498
        }
499
    } else if ([object.contentType isEqualToString:@"application/directory"]) {
500
        // Create local directory object
501
        NSLog(@"Sync::create local directory object: %@", filePath);
502
        BOOL directoryCreated = NO;
503
        if (!fileExists || (!isDirectory && [self moveToTempTrashFile:filePath])) {
504
            NSLog(@"Sync::local directory object doesn't exist: %@", filePath);
505
            error = nil;
506
            if (![fileManager createDirectoryAtPath:filePath withIntermediateDirectories:YES attributes:nil error:&error] || error) {
507
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
508
                                                        message:[NSString stringWithFormat:@"Cannot create directory at '%@'", filePath] 
509
                                                          error:error];
510
            } else {
511
                directoryCreated = YES;
512
                storedState.isDirectory = YES;
513
                [self saveLocalState];
514
            }
515
        } else {
516
            NSLog(@"Sync::local directory object exists: %@", filePath);
517
            directoryCreated = YES;
518
        }
519
        if (directoryCreated)
520
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
521
                                                  message:[NSString stringWithFormat:@"Sync: Creating directory '%@' locally (finished)", object.name]];
522
    } else if (object.bytes == 0) {
523
        // Create local object with zero length
524
        NSLog(@"Sync::create local zero length object: %@", filePath);
525
        BOOL fileCreated = NO;
526
        if (!fileExists || ((isDirectory || [PithosUtilities bytesOfFile:filePath]) && [self moveToTempTrashFile:filePath])) {
527
            NSLog(@"Sync::local zero length object doesn't exist: %@", filePath);
528
            error = nil;
529
            if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
530
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create File Error" 
531
                                                        message:[NSString stringWithFormat:@"Cannot create file at '%@'", filePath] 
532
                                                          error:error];
533
            } else {
534
                fileCreated = YES;
535
                storedState.hash = object.hash;
536
                storedState.filePath = nil;
537
                [self saveLocalState];
538
            }
539
        } else {
540
            NSLog(@"Sync::local zero length object exists: %@", filePath);
541
            fileCreated = YES;
542
        }
543
        if (fileCreated)
544
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
545
                                                  message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
546
    } else if (storedState.filePath == nil) {
547
        // Create new local object
548
        // Check first if a local copy exists
549
        if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
550
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
551
                                                  message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
552
        } else {
553
            [self increaseSyncOperationCount];
554
            __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
555
                                                                                                              object:object 
556
                                                                                                          blockIndex:0 
557
                                                                                                           blockSize:blockSize];
558
            objectRequest.delegate = self;
559
            objectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
560
            objectRequest.didFailSelector = @selector(requestFailed:);
561
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
562
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
563
                                                                    totalBytes:object.bytes 
564
                                                                  currentBytes:0];
565
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
566
                                      object, @"pithosObject", 
567
                                      [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, (NSUInteger)ceil((object.bytes +0.0)/(blockSize + 0.0)))], @"missingBlocks", 
568
                                      [NSNumber numberWithUnsignedInteger:0], @"missingBlockIndex", 
569
                                      filePath, @"filePath", 
570
                                      activity, @"activity", 
571
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
572
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
573
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
574
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
575
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
576
                                      nil];
577
            [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
578
                [activityFacility updateActivity:activity 
579
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
580
                                                  [[objectRequest.userInfo valueForKey:@"pithosObject"] name], 
581
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
582
                                      totalBytes:activity.totalBytes 
583
                                    currentBytes:(activity.currentBytes + size)];
584
            }];
585
            [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
586
        }
587
    } else {
588
        // Resume local object download
589
        // Check first if a local copy exists
590
        if ([self findLocalCopyForObjectWithHash:object.hash forFile:filePath]) {
591
            [activityFacility startAndEndActivityWithType:PithosActivityOther 
592
                                                  message:[NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name]];
593
            // Delete incomplete temp download
594
            error = nil;
595
            if (![fileManager removeItemAtPath:storedState.filePath error:&error] || error) {
596
                [PithosUtilities fileActionFailedAlertWithTitle:@"Remove File Error" 
597
                                                        message:[NSString stringWithFormat:@"Cannot remove file at '%@'", storedState.filePath] 
598
                                                          error:error];
599
            }
600
        } else {
601
            [self increaseSyncOperationCount];
602
            ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectHashmapRequestWithContainerName:containerName 
603
                                                                                                       objectName:object.name];
604
            objectRequest.delegate = self;
605
            objectRequest.didFinishSelector = @selector(downloadObjectHashMapFinished:);
606
            // The fail method for block download does exactly what we want
607
            objectRequest.didFailSelector = @selector(requestFailed:);
608
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload 
609
                                                                       message:[NSString stringWithFormat:@"Sync: Downloading '%@' (0%%)", object.name] 
610
                                                                    totalBytes:object.bytes 
611
                                                                  currentBytes:0];
612
            objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
613
                                      object, @"pithosObject", 
614
                                      filePath, @"filePath", 
615
                                      activity, @"activity", 
616
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
617
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (failed)", object.name], @"failedActivityMessage", 
618
                                      [NSString stringWithFormat:@"Sync: Downloading '%@' (100%%)", object.name], @"finishedActivityMessage", 
619
                                      [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
620
                                      [NSNumber numberWithUnsignedInteger:10], @"retries", 
621
                                      nil];
622
            [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
623
        }
624
    }
625
}
626

    
627
-(void)updateServerStateWithCurrentState:(PithosLocalObjectState *)currentState 
628
                                  object:(ASIPithosObject *)object 
629
                           localFilePath:(NSString *)filePath {
630
    [self increaseSyncOperationCount];
631
    if (currentState.isDirectory) {
632
        // Create remote directory object
633
        ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
634
                                                                                                     objectName:object.name 
635
                                                                                                           eTag:nil 
636
                                                                                                    contentType:@"application/directory" 
637
                                                                                                contentEncoding:nil 
638
                                                                                             contentDisposition:nil 
639
                                                                                                       manifest:nil 
640
                                                                                                        sharing:nil 
641
                                                                                                       isPublic:ASIPithosObjectRequestPublicIgnore 
642
                                                                                                       metadata:nil 
643
                                                                                                           data:[NSData data]];
644
        objectRequest.delegate = self;
645
        objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
646
        objectRequest.didFailSelector = @selector(requestFailed:);
647
        PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory 
648
                                                                   message:[NSString stringWithFormat:@"Sync: Creating directory '%@'", object.name]];
649
        objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
650
                                  object, @"pithosObject", 
651
                                  activity, @"activity", 
652
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (stopped)", object.name], @"stoppedActivityMessage", 
653
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (failed)", object.name], @"failedActivityMessage", 
654
                                  [NSString stringWithFormat:@"Sync: Creating directory '%@' (finished)", object.name], @"finishedActivityMessage", 
655
                                  [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
656
                                  [NSNumber numberWithUnsignedInteger:10], @"retries", 
657
                                  nil];
658
        [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
659
    } else if (!currentState.exists) {
660
        // Delete remote object
661
        NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash" 
662
                                                                        objectName:object.name];
663
        if (safeObjectName) {
664
            ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:containerName 
665
                                                                                             objectName:object.name 
666
                                                                               destinationContainerName:@"trash" 
667
                                                                                  destinationObjectName:safeObjectName 
668
                                                                                          checkIfExists:NO];
669
            if (objectRequest) {
670
                objectRequest.delegate = self;
671
                objectRequest.didFinishSelector = @selector(moveObjectToTrashFinished:);
672
                objectRequest.didFailSelector = @selector(requestFailed:);
673
                PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete 
674
                                                                           message:[NSString stringWithFormat:@"Sync: Moving to trash '%@'", object.name]];
675
                objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
676
                                          object, @"pithosObject", 
677
                                          activity, @"activity", 
678
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (stopped)", object.name], @"stoppedActivityMessage", 
679
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (failed)", object.name], @"failedActivityMessage", 
680
                                          [NSString stringWithFormat:@"Sync: Moving to trash '%@' (finished)", object.name], @"finishedActivityMessage", 
681
                                          [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
682
                                          [NSNumber numberWithUnsignedInteger:10], @"retries", 
683
                                          nil];
684
                [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
685
            } else {
686
                syncIncomplete = YES;
687
                [self decreaseSyncOperationCount];
688
            }
689
        } else {
690
            syncIncomplete = YES;
691
            [self decreaseSyncOperationCount];
692
        }
693
    } else {
694
        // Upload file to remote object
695
        NSError *error = nil;
696
        object.contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
697
        if (object.contentType == nil)
698
            object.contentType = @"application/octet-stream";
699
        if (error)
700
            NSLog(@"contentType detection error: %@", error);
701
        NSArray *hashes = nil;
702
        ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
703
                                                                                              objectName:object.name 
704
                                                                                             contentType:object.contentType 
705
                                                                                               blockSize:blockSize 
706
                                                                                               blockHash:blockHash 
707
                                                                                                 forFile:filePath 
708
                                                                                           checkIfExists:NO 
709
                                                                                                  hashes:&hashes 
710
                                                                                          sharingAccount:nil];
711
        if (objectRequest) {
712
            objectRequest.delegate = self;
713
            objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
714
            objectRequest.didFailSelector = @selector(requestFailed:);
715
            PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload 
716
                                                                       message:[NSString stringWithFormat:@"Sync: Uploading '%@' (0%%)", object.name]
717
                                                                    totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
718
                                                                  currentBytes:0];
719
            [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
720
             [NSDictionary dictionaryWithObjectsAndKeys:
721
              object, @"pithosObject", 
722
              filePath, @"filePath", 
723
              hashes, @"hashes", 
724
              [NSNumber numberWithUnsignedInteger:10], @"iteration", 
725
              activity, @"activity", 
726
              [NSString stringWithFormat:@"Sync: Uploading '%@' (stopped)", object.name], @"stoppedActivityMessage", 
727
              [NSString stringWithFormat:@"Sync: Uploading '%@' (failed)", object.name], @"failedActivityMessage", 
728
              [NSString stringWithFormat:@"Sync: Uploading '%@' (100%%)", object.name], @"finishedActivityMessage", 
729
              [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority", 
730
              [NSNumber numberWithUnsignedInteger:10], @"retries", 
731
              nil]];
732
            [queue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityNormal]];
733
        } else {
734
            syncIncomplete = YES;
735
            [self decreaseSyncOperationCount];
736
        }
737
    }
738

    
739
}
740

    
741
#pragma mark -
742
#pragma mark ASIHTTPRequestDelegate
743

    
744
- (void)listRequestFinished:(ASIPithosContainerRequest *)containerRequest {
745
    NSLog(@"Sync::list request finished: %@", containerRequest.url);
746
    if ((containerRequest.responseStatusCode == 200) || (containerRequest.responseStatusCode == 304)) {
747
        if (containerRequest.responseStatusCode == 200) {
748
            NSArray *someObjects = [containerRequest objects];
749
            if (objects == nil) {
750
                objects = [[NSMutableArray alloc] initWithArray:someObjects];
751
            } else {
752
                [objects addObjectsFromArray:someObjects];
753
            }
754
            if ([someObjects count] < 10000) {
755
                self.blockHash = [containerRequest blockHash];
756
                self.blockSize = [containerRequest blockSize];
757
                self.lastModified = [containerRequest lastModified];
758
                self.remoteObjects = [NSMutableDictionary dictionaryWithCapacity:[objects count]];
759
                for (ASIPithosObject *object in objects) {
760
                    [remoteObjects setObject:object forKey:object.name];
761
                }
762
                [objects release];
763
                objects = nil;
764
            } else {
765
                // Do an additional request to fetch more objects
766
                ASIPithosContainerRequest *newContainerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName 
767
                                                                                                                          limit:0 
768
                                                                                                                         marker:[[someObjects lastObject] name] 
769
                                                                                                                         prefix:nil 
770
                                                                                                                      delimiter:nil 
771
                                                                                                                           path:nil 
772
                                                                                                                           meta:nil 
773
                                                                                                                         shared:NO 
774
                                                                                                                          until:nil 
775
                                                                                                                ifModifiedSince:lastModified];
776
                newContainerRequest.delegate = self;
777
                newContainerRequest.didFinishSelector = @selector(listRequestFinished:);
778
                newContainerRequest.didFailSelector = @selector(listRequestFailed:);
779
                newContainerRequest.userInfo = newContainerRequest.userInfo;
780
                [(NSMutableDictionary *)newContainerRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
781
                [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
782
                return;
783
            }
784
        }
785
        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
786
                          withMessage:[containerRequest.userInfo objectForKey:@"finishedActivityMessage"]];
787
        NSFileManager *fileManager = [NSFileManager defaultManager];
788
        NSError *error = nil;
789
        NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:containerDirectoryPath error:&error];
790
        if (error) {
791
            [PithosUtilities fileActionFailedAlertWithTitle:@"Directory Contents Error" 
792
                                                    message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", containerDirectoryPath] 
793
                                                      error:error];
794
            [activityFacility startAndEndActivityWithType:PithosActivityOther message:@"Sync: Failed to read contents of sync directory"];
795
            @synchronized(self) {
796
                // Since the local listing failed, the operation finished and the sync cycle is completeted unsuccesfully
797
                syncOperationCount = 0;
798
                if (newSyncRequested)
799
                    [self sync];
800
            }
801
            return;
802
        }
803
        self.currentLocalObjectStates = [NSMutableDictionary dictionaryWithCapacity:[subPaths count]];
804
        for (NSString *objectName in subPaths) {
805
            if (![storedLocalObjectStates objectForKey:objectName]) {
806
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
807
            }
808
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
809
            [currentLocalObjectStates setObject:[PithosLocalObjectState localObjectStateWithFile:filePath 
810
                                                                                       blockHash:blockHash 
811
                                                                                       blockSize:blockSize] 
812
                                         forKey:filePath];
813
        }
814
        [self saveLocalState];
815

    
816
        for (NSString *objectName in remoteObjects) {
817
            if (![storedLocalObjectStates objectForKey:objectName])
818
                [storedLocalObjectStates setObject:[PithosLocalObjectState localObjectState] forKey:objectName];
819
        }
820

    
821
        for (NSString *objectName in [[storedLocalObjectStates allKeys] sortedArrayUsingSelector:@selector(compare:)]) {
822
            NSString *filePath = [containerDirectoryPath stringByAppendingPathComponent:objectName];
823
            if ([objectName hasSuffix:@"/"])
824
                filePath = [filePath stringByAppendingString:@":"];
825
            ASIPithosObject *object = [ASIPithosObject object];
826
            object.name = objectName;
827
            NSLog(@"Sync::object name: %@", objectName);
828
            
829
            PithosLocalObjectState *storedLocalObjectState = [storedLocalObjectStates objectForKey:object.name];
830
            PithosLocalObjectState *currentLocalObjectState = [currentLocalObjectStates objectForKey:filePath];
831
            if (!currentLocalObjectState)
832
                currentLocalObjectState = [PithosLocalObjectState localObjectStateWithFile:filePath 
833
                                                                                 blockHash:blockHash 
834
                                                                                 blockSize:blockSize];
835
            if (currentLocalObjectState.isDirectory)
836
                object.contentType = @"application/directory";
837
            
838
            PithosLocalObjectState *remoteObjectState = [PithosLocalObjectState localObjectState];
839
            ASIPithosObject *remoteObject = [remoteObjects objectForKey:objectName];
840
            if (remoteObject) {
841
                if ([remoteObject.contentType isEqualToString:@"application/directory"]) {
842
                    remoteObjectState.isDirectory = YES;
843
                    object.contentType = @"application/directory";
844
                } else {
845
                    remoteObjectState.hash = remoteObject.hash;
846
                }
847
            }
848

    
849
            BOOL localStateHasChanged = ![currentLocalObjectState isEqualToState:storedLocalObjectState];
850
            BOOL serverStateHasChanged = ![remoteObjectState isEqualToState:storedLocalObjectState];
851
            NSLog(@"Sync::localStateHasChanged: %d, serverStateHasChanged: %d", localStateHasChanged, serverStateHasChanged);
852
            if (!localStateHasChanged) {
853
                // Local state hasn't changed
854
                if (serverStateHasChanged) {
855
                    // Server state has changed
856
                    // Update local state to match that of the server 
857
                    object.bytes = remoteObject.bytes;
858
                    object.version = remoteObject.version;
859
                    object.contentType = remoteObject.contentType;
860
                    object.hash = remoteObject.hash;
861
                    [self updateLocalStateWithObject:object localFilePath:filePath];
862
                } else if (!remoteObject && !currentLocalObjectState.exists) {
863
                    // Server state hasn't changed
864
                    // If the object doesn't exist neither in the server or locally, it should be removed from the stored local objects
865
                    [storedLocalObjectStates removeObjectForKey:objectName];
866
                    [self saveLocalState];
867
                }
868
            } else {
869
                // Local state has changed
870
                if (!serverStateHasChanged) {
871
                    // Server state hasn't changed
872
                    [self updateServerStateWithCurrentState:currentLocalObjectState 
873
                                                     object:object 
874
                                              localFilePath:filePath];
875
                } else {
876
                    // Server state has also changed
877
                    if (remoteObjectState.isDirectory && currentLocalObjectState.isDirectory) {
878
                        // Both did the same change (directory)
879
                        storedLocalObjectState.isDirectory = YES;
880
                        [self saveLocalState];
881
                    } else if ([remoteObjectState isEqualToState:currentLocalObjectState]) {
882
                        // Both did the same change (object edit or delete)
883
                        if (!remoteObjectState.exists)
884
                            [storedLocalObjectStates removeObjectForKey:object.name];
885
                        else
886
                            storedLocalObjectState.hash = remoteObjectState.hash;
887
                        [self saveLocalState];
888
                    } else {
889
                        // Conflict, we ask the user which change to keep
890
                        NSString *informativeText;
891
                        NSString *firstButtonText;
892
                        NSString *secondButtonText;
893
                        
894
                        if (!remoteObjectState.exists) {
895
                            // Remote object has been deleted
896
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified locally, while it has been deleted from server.", object.name ];
897
                            firstButtonText = @"Delete local file";
898
                            secondButtonText = @"Upload file to server";
899
                        } else if (!currentLocalObjectState.exists) {
900
                            informativeText = [NSString stringWithFormat:@"'%@' has been modified on the server, while it has been deleted locally.", object.name];
901
                            firstButtonText = @"Download file from server";
902
                            secondButtonText = @"Delete file on server";
903
                        } else {
904
                            informativeText = [NSString stringWithFormat:@"'%@' has been modifed both locally and on the server.", object.name];
905
                            firstButtonText = @"Keep server version";
906
                            secondButtonText = @"Keep local version";
907
                        }
908
                        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
909
                        [alert setMessageText:@"Conflict"];
910
                        [alert setInformativeText:informativeText];
911
                        [alert addButtonWithTitle:firstButtonText];
912
                        [alert addButtonWithTitle:secondButtonText];
913
                        [alert addButtonWithTitle:@"Do nothing"];
914
                        NSInteger choice = [alert runModal];
915
                        if (choice == NSAlertFirstButtonReturn) {
916
                            object.bytes = remoteObject.bytes;
917
                            object.version = remoteObject.version;
918
                            object.contentType = remoteObject.contentType;
919
                            object.hash = remoteObject.hash;
920
                            [self updateLocalStateWithObject:object localFilePath:filePath];
921
                        } if (choice == NSAlertSecondButtonReturn) {
922
                            [self updateServerStateWithCurrentState:currentLocalObjectState 
923
                                                             object:object 
924
                                                      localFilePath:filePath];
925
                        }
926
                    }
927
                }
928
            }
929
        }
930
        @synchronized(self) {
931
            [self decreaseSyncOperationCount];
932
            if (newSyncRequested && !syncOperationCount)
933
                [self sync];
934
        }
935
    } else {
936
        NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
937
        if (retries > 0) {
938
            ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
939
            [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
940
            [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
941
        } else {
942
            [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
943
                              withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
944
            @synchronized(self) {
945
                // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
946
                syncOperationCount = 0;
947
                if (newSyncRequested)
948
                    [self sync];
949
            }
950
        }
951
    }
952
}
953

    
954
- (void)listRequestFailed:(ASIPithosContainerRequest *)containerRequest {
955
    if ([containerRequest isCancelled]) {
956
        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
957
                          withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
958
        [objects release];
959
        objects = nil;
960
        @synchronized(self) {
961
            syncOperationCount = 0;
962
        }
963
        return;
964
    }
965
    // If the server listing fails, the sync should start over, so just retrying is enough
966
    NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
967
    if (retries > 0) {
968
        ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
969
        [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
970
        [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
971
    } else {
972
        [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"] 
973
                          withMessage:[containerRequest.userInfo objectForKey:@"failedActivityMessage"]];
974
        [objects release];
975
        objects = nil;
976
        @synchronized(self) {
977
            // Since the server listing failed in all retries, the operation finished and the sync cycle is completeted unsuccesfully
978
            syncOperationCount = 0;
979
            if (newSyncRequested)
980
                [self sync];
981
        }
982
    }
983
}
984

    
985
- (void)downloadObjectBlockFinished:(ASIPithosObjectRequest *)objectRequest {
986
    NSLog(@"Sync::download object block finished: %@", objectRequest.url);
987
    if (objectRequest.responseStatusCode == 206) {
988
        ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
989
        NSFileManager *fileManager = [NSFileManager defaultManager];
990
        NSError *error;
991
        PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
992
        
993
        NSString *downloadsDirPath = self.tempDownloadsDirPath;
994
        if (!downloadsDirPath) {
995
            [activityFacility endActivity:activity 
996
                              withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
997
            @synchronized(self) {
998
                syncIncomplete = YES;
999
                [self decreaseSyncOperationCount];
1000
                if (newSyncRequested && !syncOperationCount)
1001
                    [self sync];
1002
            }
1003
            return;
1004
        }
1005
        
1006
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1007
        if ((storedState.filePath == nil) || ![fileManager fileExistsAtPath:storedState.filePath]) {
1008
            NSString *tempFileTemplate = [downloadsDirPath stringByAppendingPathComponent:@"download.XXXXXX"];
1009
            const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
1010
            char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
1011
            strcpy(tempFileNameCString, tempFileTemplateCString);
1012
            int fileDescriptor = mkstemp(tempFileNameCString);
1013
            NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
1014
            free(tempFileNameCString);
1015
            if (fileDescriptor == -1) {
1016
                [PithosUtilities fileActionFailedAlertWithTitle:@"Create Temporary File Error" 
1017
                                                        message:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", storedState.filePath] 
1018
                                                          error:nil];
1019
                [activityFacility endActivity:activity 
1020
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1021
                @synchronized(self) {
1022
                    syncIncomplete = YES;
1023
                    [self decreaseSyncOperationCount];
1024
                    if (newSyncRequested && !syncOperationCount)
1025
                        [self sync];
1026
                }
1027
                return;
1028
            }
1029
            close(fileDescriptor);
1030
            storedState.filePath = tempFilePath;
1031
            [self saveLocalState];
1032
        }
1033
        
1034

    
1035
        NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1036
        NSFileHandle *tempFileHandle = [NSFileHandle fileHandleForWritingAtPath:storedState.filePath];
1037
        [tempFileHandle seekToFileOffset:missingBlockIndex*blockSize];
1038
        [tempFileHandle writeData:[objectRequest responseData]];
1039
        [tempFileHandle closeFile];
1040

    
1041
        NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1042
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1043
        if (missingBlockIndex == NSNotFound) {
1044
            NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1045
            NSString *dirPath = [filePath stringByDeletingLastPathComponent];
1046
            if ([fileManager fileExistsAtPath:filePath] && ![self moveToTempTrashFile:filePath]) {
1047
                [activityFacility endActivity:activity 
1048
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1049
                @synchronized(self) {
1050
                    syncIncomplete = YES;
1051
                    [self decreaseSyncOperationCount];
1052
                    if (newSyncRequested && !syncOperationCount)
1053
                        [self sync];
1054
                }
1055
                return;
1056
            } else if (![fileManager fileExistsAtPath:dirPath]) {
1057
                // File doesn't exist but also the containing directory doesn't exist
1058
                // In most cases this should have been resolved as an update of the corresponding local object,
1059
                // but it never hurts to check
1060
                error = nil;
1061
                [fileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
1062
                if (error != nil) {
1063
                    [PithosUtilities fileActionFailedAlertWithTitle:@"Create Directory Error" 
1064
                                                            message:[NSString stringWithFormat:@"Cannot create directory at '%@'", dirPath] 
1065
                                                              error:error];
1066
                    [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1067
                                      withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1068
                    @synchronized(self) {
1069
                        syncIncomplete = YES;
1070
                        [self decreaseSyncOperationCount];
1071
                        if (newSyncRequested && !syncOperationCount)
1072
                            [self sync];
1073
                    }
1074
                    return;
1075
                }
1076
            }
1077
            // Move file from tmp download
1078
            error = nil;
1079
            [fileManager moveItemAtPath:storedState.filePath toPath:filePath error:&error];
1080
            if (error != nil) {
1081
                [PithosUtilities fileActionFailedAlertWithTitle:@"Move File Error" 
1082
                                                        message:[NSString stringWithFormat:@"Cannot move file at '%@' to '%@'", storedState.filePath, filePath] 
1083
                                                          error:error];
1084
                [activityFacility endActivity:activity 
1085
                                  withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1086
                @synchronized(self) {
1087
                    syncIncomplete = YES;
1088
                    [self decreaseSyncOperationCount];
1089
                    if (newSyncRequested && !syncOperationCount)
1090
                        [self sync];
1091
                }
1092
                return;
1093
            }
1094
            [activityFacility endActivity:activity 
1095
                              withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"] 
1096
                               totalBytes:activity.totalBytes 
1097
                             currentBytes:activity.totalBytes];
1098

    
1099
            storedState.hash = object.hash;
1100
            storedState.filePath = nil;
1101
            [self saveLocalState];
1102
            
1103
            @synchronized(self) {
1104
                [self decreaseSyncOperationCount];
1105
                if (newSyncRequested && !syncOperationCount)
1106
                    [self sync];
1107
            }
1108
            return;
1109
        } else {
1110
            if (newSyncRequested) {
1111
                [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1112
                                  withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1113
                @synchronized(self) {
1114
                    syncIncomplete = YES;
1115
                    [self decreaseSyncOperationCount];
1116
                    if (!syncOperationCount)
1117
                        [self sync];
1118
                }
1119
                return;
1120
            } else {
1121
                __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1122
                                                                                                                     object:object 
1123
                                                                                                                 blockIndex:missingBlockIndex 
1124
                                                                                                                  blockSize:blockSize];
1125
                newObjectRequest.delegate = self;
1126
                newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1127
                newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1128
                newObjectRequest.userInfo = objectRequest.userInfo;
1129
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1130
                [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1131
                [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1132
                    [activityFacility updateActivity:activity 
1133
                                         withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1134
                                                      object.name, 
1135
                                                      (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1136
                                          totalBytes:activity.totalBytes 
1137
                                        currentBytes:(activity.currentBytes + size)];
1138
                }];
1139
                [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1140
            }
1141
        }
1142
    } else if (objectRequest.responseStatusCode == 412) {
1143
        // The object has changed on the server
1144
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1145
                          withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1146
        @synchronized(self) {
1147
            syncIncomplete = YES;
1148
            [self decreaseSyncOperationCount];
1149
            if (newSyncRequested && !syncOperationCount)
1150
                [self sync];
1151
        }
1152
    } else {
1153
        [self requestFailed:objectRequest];
1154
    }
1155
}
1156

    
1157
- (void)downloadObjectHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1158
    NSLog(@"Sync::download object hashmap finished: %@", objectRequest.url);
1159
    if (objectRequest.responseStatusCode == 200) {
1160
        if (newSyncRequested) {
1161
            [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1162
                              withMessage:[objectRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1163
            @synchronized(self) {
1164
                syncIncomplete = YES;
1165
                [self decreaseSyncOperationCount];
1166
                if (!syncOperationCount)
1167
                    [self sync];
1168
            }
1169
            return;
1170
        } else {
1171
            ASIPithosObject *object = [objectRequest.userInfo objectForKey:@"pithosObject"];
1172
            PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:object.name];
1173
            if ([PithosUtilities bytesOfFile:storedState.filePath] > object.bytes)
1174
                [[NSFileHandle fileHandleForWritingAtPath:storedState.filePath] truncateFileAtOffset:object.bytes];
1175
            PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1176
            NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForFile:storedState.filePath 
1177
                                                                    blockSize:blockSize 
1178
                                                                    blockHash:blockHash 
1179
                                                                   withHashes:[objectRequest hashes]];
1180
            NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1181
            [activityFacility endActivity:activity 
1182
                              withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1183
                                           object.name, 
1184
                                           (100*(activity.totalBytes - [missingBlocks count]*blockSize + 0.0)/(activity.totalBytes + 0.0))] 
1185
                               totalBytes:activity.totalBytes 
1186
                             currentBytes:(activity.totalBytes - [missingBlocks count]*blockSize)];
1187

    
1188
            __block ASIPithosObjectRequest *newObjectRequest = [PithosUtilities objectBlockDataRequestWithContainerName:containerName 
1189
                                                                                                                 object:object 
1190
                                                                                                             blockIndex:missingBlockIndex 
1191
                                                                                                              blockSize:blockSize];
1192
            newObjectRequest.delegate = self;
1193
            newObjectRequest.didFinishSelector = @selector(downloadObjectBlockFinished:);
1194
            newObjectRequest.didFailSelector = @selector(downloadObjectBlockOrHashMapFailed:);
1195
            newObjectRequest.userInfo = objectRequest.userInfo;
1196
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1197
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1198
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1199
            [newObjectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1200
                [activityFacility updateActivity:activity 
1201
                                     withMessage:[NSString stringWithFormat:@"Sync: Downloading '%@' (%.0f%%)", 
1202
                                                  object.name, 
1203
                                                  (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1204
                                      totalBytes:activity.totalBytes 
1205
                                    currentBytes:(activity.currentBytes + size)];
1206
            }];
1207
            [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1208
        }
1209
    } else {
1210
        [self requestFailed:objectRequest];
1211
    }
1212
}
1213

    
1214
- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1215
    NSLog(@"Sync::upload directory object finished: %@", objectRequest.url);
1216
    if (objectRequest.responseStatusCode == 201) {
1217
        PithosLocalObjectState *storedState = [storedLocalObjectStates objectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1218
        storedState.isDirectory = YES;
1219
        [self saveLocalState];
1220
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1221
                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1222
        @synchronized(self) {
1223
            [self decreaseSyncOperationCount];
1224
            if (newSyncRequested && !syncOperationCount)
1225
                [self sync];
1226
        }
1227
    } else {
1228
        [self requestFailed:objectRequest];
1229
    }
1230
}
1231

    
1232
- (void)moveObjectToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1233
    NSLog(@"Sync::move object to trash finished: %@", objectRequest.url);
1234
    if (objectRequest.responseStatusCode == 201) {
1235
        [storedLocalObjectStates removeObjectForKey:[[objectRequest.userInfo objectForKey:@"pithosObject"] name]];
1236
        [self saveLocalState];
1237
        [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"] 
1238
                          withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1239
        @synchronized(self) {
1240
            [self decreaseSyncOperationCount];
1241
            if (newSyncRequested && !syncOperationCount)
1242
                [self sync];
1243
        }
1244
    } else {
1245
        [self requestFailed:objectRequest];
1246
    }
1247
}
1248

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

    
1326
- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1327
    NSLog(@"Sync::upload of missing block finished: %@", containerRequest.url);
1328
    if (containerRequest.responseStatusCode == 202) {
1329
        ASIPithosObject *object = [containerRequest.userInfo objectForKey:@"pithosObject"];
1330
        PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1331
        NSUInteger totalBytes = activity.totalBytes;
1332
        NSUInteger currentBytes = activity.currentBytes + blockSize;
1333
        if (currentBytes > totalBytes)
1334
            currentBytes = totalBytes;
1335
        [activityFacility updateActivity:activity 
1336
                             withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(currentBytes + 0.0)/(totalBytes + 0.0))] 
1337
                              totalBytes:totalBytes 
1338
                            currentBytes:currentBytes];
1339
        NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1340
        NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1341
        missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1342
        if (missingBlockIndex == NSNotFound) {
1343
            NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1344
            ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName 
1345
                                                                                                     objectName:object.name 
1346
                                                                                                    contentType:object.contentType 
1347
                                                                                                      blockSize:blockSize 
1348
                                                                                                      blockHash:blockHash
1349
                                                                                                        forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1350
                                                                                                  checkIfExists:NO 
1351
                                                                                                         hashes:&hashes 
1352
                                                                                                 sharingAccount:nil];
1353
            newObjectRequest.delegate = self;
1354
            newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1355
            newObjectRequest.didFailSelector = @selector(requestFailed:);
1356
            newObjectRequest.userInfo = containerRequest.userInfo;
1357
            [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1358
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1359
            [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1360
            [queue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1361
        } else {
1362
            if (newSyncRequested) {
1363
                [activityFacility endActivity:activity 
1364
                                  withMessage:[containerRequest.userInfo objectForKey:@"stoppedActivityMessage"]];
1365
                @synchronized(self) {
1366
                    syncIncomplete = YES;
1367
                    [self decreaseSyncOperationCount];
1368
                    if (!syncOperationCount)
1369
                        [self sync];
1370
                }
1371
                return;
1372
            } else {
1373
                __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:containerName
1374
                                                                                                                            blockSize:blockSize
1375
                                                                                                                              forFile:[containerRequest.userInfo objectForKey:@"filePath"] 
1376
                                                                                                                    missingBlockIndex:missingBlockIndex 
1377
                                                                                                                       sharingAccount:nil];
1378
                newContainerRequest.delegate = self;
1379
                newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1380
                newContainerRequest.didFailSelector = @selector(requestFailed:);
1381
                newContainerRequest.userInfo = containerRequest.userInfo;
1382
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1383
                [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1384
                [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1385
                    [activityFacility updateActivity:activity 
1386
                                         withMessage:[NSString stringWithFormat:@"Sync: Uploading '%@' (%.0f%%)", object.name, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))] 
1387
                                          totalBytes:activity.totalBytes 
1388
                                        currentBytes:(activity.currentBytes + size)];
1389
                }];
1390
                [queue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1391
            }
1392
        }
1393
    } else {
1394
        [self requestFailed:containerRequest];
1395
    }
1396
}
1397

    
1398
- (void)requestFailed:(ASIPithosRequest *)request {
1399
    if ([request isCancelled]) {
1400
        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1401
                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1402
        syncIncomplete = YES;
1403
        [self decreaseSyncOperationCount];
1404
        return;
1405
    }
1406
    if (newSyncRequested) {
1407
        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1408
                          withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1409
        @synchronized(self) {
1410
            syncIncomplete = YES;
1411
            [self decreaseSyncOperationCount];
1412
            if (!syncOperationCount)
1413
                [self sync];
1414
        }
1415
        return;
1416
    }
1417
    NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1418
    if (retries > 0) {
1419
        ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1420
        [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1421
        [queue addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1422
    } else {
1423
        [activityFacility endActivity:[request.userInfo objectForKey:@"activity"] 
1424
                          withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1425
        syncIncomplete = YES;
1426
        [self decreaseSyncOperationCount];
1427
    }
1428
}
1429

    
1430

    
1431
@end