// // PithosContainerNode.m // pithos-macos // // Copyright 2011-2012 GRNET S.A. All rights reserved. // // Redistribution and use in source and binary forms, with or // without modification, are permitted provided that the following // conditions are met: // // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. // // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials // provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // The views and conclusions contained in the software and // documentation are those of the authors and should not be // interpreted as representing official policies, either expressed // or implied, of GRNET S.A. #import "PithosContainerNode.h" #import "PithosObjectNode.h" #import "PithosSubdirNode.h" #import "ASIPithos.h" #import "ASIPithosContainerRequest.h" #import "ASIPithosContainer.h" #import "ASIPithosObject.h" #import "ASIDownloadCache.h" #import "PithosUtilities.h" #import "PithosContainerNodeInfoController.h" #import "PithosActivityFacility.h" static NSImage *sharedIcon = nil; @implementation PithosContainerNode @synthesize pithos, pithosContainer, prefix; @synthesize policyVersioning, policyQuota; + (void)initialize { if (self == [PithosContainerNode class]) sharedIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)]; } #pragma mark - #pragma mark Object Lifecycle - (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon { if ((self = [super init])) { self.pithos = aPithos; self.pithosContainer = aPithosContainer; prefix = nil; self.icon = anIcon; } return self; } - (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer { return [self initWithPithos:aPithos pithosContainer:aPithosContainer icon:nil]; } - (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName icon:(NSImage *)anIcon { ASIPithosContainer *container = [ASIPithosContainer container]; container.name = aContainerName; return [self initWithPithos:aPithos pithosContainer:container icon:anIcon]; } - (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName { return [self initWithPithos:aPithos containerName:aContainerName icon:nil]; } - (void)dealloc { [containerRequest clearDelegatesAndCancel]; [refreshMetadataContainerRequest clearDelegatesAndCancel]; [applyMetadataContainerRequest clearDelegatesAndCancel]; } #pragma mark - #pragma mark Properties - (void)setPithos:(ASIPithos *)aPithos { if (aPithos && ![aPithos isEqualTo:pithos]) { pithos = aPithos; url = nil; } } - (NSString *)url { if (url == nil) url = [[NSString alloc] initWithFormat:@"%@/%@%@", (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), pithosContainer.name, (shared ? @"?shared" : @"")]; return url; } - (NSArray *)children { @synchronized(self) { switch (freshness) { case PithosNodeStateFresh: break; case PithosNodeStateRefreshNeeded: freshness = PithosNodeStateRefreshing; containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos containerName:pithosContainer.name limit:0 marker:nil prefix:prefix delimiter:@"/" path:nil meta:nil shared:shared until:nil]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; else if (!forcedRefresh) containerRequest.downloadCache = [ASIDownloadCache sharedCache]; containerRequest.delegate = self; containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", nil]; [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; break; case PithosNodeStateRefreshing: break; case PithosNodeStateRefreshFinished: if (newChildren) { children = newChildren; newChildren = nil; } freshness = PithosNodeStateFresh; default: break; } return children; } } - (NSString *)displayName { return [pithosContainer.name copy]; } - (void)setDisplayName:(NSString *)aDisplayName { } - (NSImage *)icon { if (icon == nil) { if ([pithosContainer.name isEqualToString:@"pithos"]) icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]; else if ([pithosContainer.name isEqualToString:@"trash"]) icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]; else icon = sharedIcon; } return icon; } - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer { if (![pithosContainer isEqualTo:aPithosContainer]) { pithosContainer = aPithosContainer; } if (pithosContainer.policy) { self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"]; self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]]; } else { self.policyVersioning = @"manual"; self.policyQuota = [NSNumber numberWithLongLong:0]; } } - (void)setLimitedPithosContainer:(ASIPithosContainer *)aPithosContainer { if (![pithosContainer isEqualTo:aPithosContainer]) { self.pithosContainer.name = aPithosContainer.name; self.pithosContainer.count = aPithosContainer.count; self.pithosContainer.bytes = aPithosContainer.bytes; self.pithosContainer.lastModified = aPithosContainer.lastModified; self.pithosContainer.untilTimestamp = aPithosContainer.untilTimestamp; if (!pithosNodeInfoController) { self.pithosContainer.policy = aPithosContainer.policy; self.pithosContainer = pithosContainer; } } } #pragma mark - #pragma mark ASIHTTPRequestDelegate - (void)containerRequestFailed:(ASIPithosContainerRequest *)request { @autoreleasepool { NSString *message; NSError *error = [containerRequest error]; if (error) message = [NSString stringWithFormat:@"Container listing %@ failed: %@", containerRequest.url, [error localizedDescription]]; else message = [NSString stringWithFormat:@"Container listing %@ failed: (%d) %@", containerRequest.url, containerRequest.responseStatusCode, containerRequest.responseStatusMessage]; dispatch_async(dispatch_get_main_queue(), ^{ [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message]; }); NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue]; if (retries > 0) { ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest]; [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"]; containerRequest = newContainerRequest; [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous]; } else { newChildren = nil; containerRequest = nil; objects = nil; forcedRefresh = NO; @synchronized(self) { freshness = PithosNodeStateRefreshNeeded; } } } } - (void)containerRequestFinished:(ASIPithosContainerRequest *)request { @autoreleasepool { DLog(@"List container finished: %@", [containerRequest url]); DLog(@"Cached: %d", [containerRequest didUseCachedResponse]); if (containerRequest.responseStatusCode == 200) { if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) { pithosContainer.blockHash = [containerRequest blockHash]; pithosContainer.blockSize = [containerRequest blockSize]; } NSArray *someObjects = [containerRequest objects]; if (objects == nil) { objects = [[NSMutableArray alloc] initWithArray:someObjects]; } else { [objects addObjectsFromArray:someObjects]; } if ([someObjects count] < 10000) { if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) { // Save new children DLog(@"using newChildren"); newChildren = [[NSMutableArray alloc] init]; NSArray *objectNames = [objects valueForKey:@"name"]; NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet]; BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]); for (ASIPithosObject *object in objects) { if (!isSubdirNode || ([object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]] && ([object.name length] > [((PithosSubdirNode *)self).prefix length] + 1))) { // The check above removes false objects due to trailing slash or same prefix if (object.subdir) { NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]]; if ((sameNameObjectIndex == NSNotFound) || ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) { PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithos:pithos pithosContainer:pithosContainer pithosObject:object]; node.parent = self; node.shared = shared; node.sharingAccount = sharingAccount; node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName; node.pithosAccountManager = pithosAccountManager; if (children) { NSUInteger oldIndex = [children indexOfObject:node]; if (oldIndex != NSNotFound) { // Use the same pointer value, if possible node = [children objectAtIndex:oldIndex]; node.pithosContainer = pithosContainer; // node.pithosObject = object; [node setLimitedPithosObject:object]; [keptNodes addIndex:oldIndex]; } } if (sharingAccount) node.pithosObject.allowedTo = @"read"; [newChildren addObject:node]; } } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) { PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithos:pithos pithosContainer:pithosContainer pithosObject:object]; node.parent = self; node.shared = shared; node.sharingAccount = sharingAccount; node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName; node.pithosAccountManager = pithosAccountManager; if (children) { NSUInteger oldIndex = [children indexOfObject:node]; if (oldIndex != NSNotFound) { // Use the same pointer value, if possible node = [children objectAtIndex:oldIndex]; node.pithosContainer = pithosContainer; // node.pithosObject = object; [node setLimitedPithosObject:object]; [keptNodes addIndex:oldIndex]; } } [newChildren addObject:node]; } else { PithosObjectNode *node = [[PithosObjectNode alloc] initWithPithos:pithos pithosContainer:pithosContainer pithosObject:object]; node.parent = self; node.shared = shared; node.sharingAccount = sharingAccount; node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName; node.pithosAccountManager = pithosAccountManager; if (children) { NSUInteger oldIndex = [children indexOfObject:node]; if (oldIndex != NSNotFound) { // Use the same pointer value, if possible node = [children objectAtIndex:oldIndex]; node.pithosContainer = pithosContainer; // node.pithosObject = object; [node setLimitedPithosObject:object]; [keptNodes addIndex:oldIndex]; } } [newChildren addObject:node]; } } } [[children objectsAtIndexes: [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){ if ([keptNodes containsIndex:idx]) return NO; return YES; }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)]; } // Else cache was used and all results were fetched during this request, so existing children can be reused containerRequest = nil; objects = nil; forcedRefresh = NO; @synchronized(self) { freshness = PithosNodeStateRefreshFinished; } [self postChildrenUpdatedNotificationName]; } else { // Do an additional request to fetch more objects containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos containerName:pithosContainer.name limit:0 marker:[[someObjects lastObject] name] prefix:prefix delimiter:@"/" path:nil meta:nil shared:shared until:nil]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; else if (!forcedRefresh) containerRequest.downloadCache = [ASIDownloadCache sharedCache]; containerRequest.delegate = self; containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", nil]; [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; } } else if (containerRequest.responseStatusCode == 304) { // Container is not modified, so existing children can be reused containerRequest = nil; objects = nil; forcedRefresh = NO; @synchronized(self) { freshness = PithosNodeStateRefreshFinished; } [self postChildrenUpdatedNotificationName]; } else { [self containerRequestFailed:containerRequest]; } } } - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request { @autoreleasepool { DLog(@"URL: %@", [request url]); DLog(@"cached: %d", [request didUseCachedResponse]); if ([request isEqualTo:applyMetadataContainerRequest]) { @synchronized(self) { applyMetadataContainerRequest = nil; } [self refreshInfo]; } else if ([request isEqualTo:refreshMetadataContainerRequest]) { [[pithosNodeInfoController window] makeFirstResponder:nil]; self.pithosContainer = [refreshMetadataContainerRequest container]; @synchronized(self) { refreshMetadataContainerRequest = nil; } } } } - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request { @autoreleasepool { NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue]; if (retries > 0) { ASIPithosContainerRequest *newRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:request]; [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"]; if ([request isEqualTo:applyMetadataContainerRequest]) { @synchronized(self) { applyMetadataContainerRequest = newRequest; } } else if ([request isEqualTo:refreshMetadataContainerRequest]) { @synchronized(self) { refreshMetadataContainerRequest = newRequest; } } [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous]; } else { if ([request isEqualTo:applyMetadataContainerRequest]) { [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest]; @synchronized(self) { applyMetadataContainerRequest = nil; } } else if ([request isEqualTo:refreshMetadataContainerRequest]) { [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest]; @synchronized(self) { refreshMetadataContainerRequest = nil; } } } } } #pragma mark - #pragma mark Info - (void)applyInfo { @synchronized(self) { if (applyMetadataContainerRequest == nil) { [[pithosNodeInfoController window] makeFirstResponder:nil]; applyMetadataContainerRequest = [ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithos containerName:pithosContainer.name policy:[NSDictionary dictionaryWithObjectsAndKeys: policyVersioning, @"versioning", [policyQuota stringValue], @"quota", nil] metadata:pithosContainer.metadata update:NO]; applyMetadataContainerRequest.delegate = self; applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", nil]; [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous]; } } } - (void)refreshInfo { @synchronized(self) { if (refreshMetadataContainerRequest == nil) { refreshMetadataContainerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos containerName:pithosContainer.name]; refreshMetadataContainerRequest.delegate = self; refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", nil]; if (!sharingAccount) refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache]; [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous]; } } } #pragma mark - #pragma mark Actions - (void)showPithosNodeInfo:(id)sender { if (!pithosNodeInfoController) { pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self]; [self refreshInfo]; } [pithosNodeInfoController showWindow:sender]; [[pithosNodeInfoController window] makeKeyAndOrderFront:sender]; [NSApp activateIgnoringOtherApps:YES]; } @end