// // PithosContainerNode.m // pithos-macos // // Copyright 2011 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 "ASIPithosContainerRequest.h" #import "ASIPithosContainer.h" #import "ASIPithosObject.h" #import "ASIDownloadCache.h" #import "PithosFileUtilities.h" #import "PithosContainerNodeInfoController.h" static NSImage *sharedIcon = nil; @implementation PithosContainerNode @synthesize pithosContainer, prefix; @synthesize policyVersioning, policyQuota; + (void)initialize { if (self == [PithosContainerNode class]) sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain]; } #pragma mark - #pragma mark Object Lifecycle - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon { if ((self = [super init])) { self.pithosContainer = aPithosContainer; prefix = nil; self.icon = anIcon; childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated"; } return self; } - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer { return [self initWithPithosContainer:aPithosContainer icon:nil]; } - (id)initWithContainerName:(NSString *)aContainerName icon:(NSImage *)anIcon { ASIPithosContainer *container = [ASIPithosContainer container]; container.name = aContainerName; return [self initWithPithosContainer:container icon:anIcon]; } - (id)initWithContainerName:(NSString *)aContainerName { return [self initWithContainerName:aContainerName icon:nil]; } - (void)dealloc { [containerRequest clearDelegatesAndCancel]; [containerRequest release]; [refreshMetadataContainerRequest clearDelegatesAndCancel]; [refreshMetadataContainerRequest release]; [applyMetadataContainerRequest clearDelegatesAndCancel]; [applyMetadataContainerRequest release]; [policyQuota release]; [policyVersioning release]; [childrenUpdatedNotificationName release]; [prefix release]; [objects release]; [pithosContainer release]; [super dealloc]; } #pragma mark - #pragma mark Properties - (NSString *)url { if (url == nil) url = [[NSString alloc] initWithFormat:@"%@/%@%@", (sharingAccount ? [ASIPithosRequest storageURLWithAuthUser:sharingAccount] : [ASIPithosRequest storageURL]), pithosContainer.name, (shared ? @"?shared" : @"")]; return url; } - (NSArray *)children { @synchronized(self) { switch (freshness) { case PithosNodeStateFresh: break; case PithosNodeStateRefreshNeeded: freshness = PithosNodeStateRefreshing; containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name limit:0 marker:nil prefix:prefix delimiter:@"/" path:nil meta:nil shared:shared until:nil] retain]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount]; containerRequest.delegate = self; containerRequest.didFinishSelector = @selector(containerRequestFinished:); containerRequest.didFailSelector = @selector(containerRequestFailed:); if (!forcedRefresh) containerRequest.downloadCache = [ASIDownloadCache sharedCache]; [containerRequest startAsynchronous]; break; case PithosNodeStateRefreshing: break; case PithosNodeStateRefreshFinished: if (newChildren) { [children release]; children = newChildren; newChildren = nil; } freshness = PithosNodeStateFresh; default: break; } return children; } } - (NSString *)displayName { return [[pithosContainer.name copy] autorelease]; } - (void)setDisplayName:(NSString *)aDisplayName { } - (NSImage *)icon { if (icon == nil) { if ([pithosContainer.name isEqualToString:@"pithos"]) icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)] retain]; else if ([pithosContainer.name isEqualToString:@"trash"]) icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)] retain]; else icon = [sharedIcon retain]; } return icon; } - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer { if (![pithosContainer isEqualTo:aPithosContainer]) { [pithosContainer release]; pithosContainer = [aPithosContainer retain]; } 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]; } } #pragma mark - #pragma mark ASIHTTPRequestDelegate - (void)containerRequestFinished:(ASIPithosContainerRequest *)request { NSLog(@"URL: %@", [containerRequest url]); NSLog(@"cached: %d", [containerRequest didUseCachedResponse]); 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 NSLog(@"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:@"/"]]) { // 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) || ![[[objects objectAtIndex:sameNameObjectIndex] contentType] isEqualToString:@"application/directory"]) { PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease]; node.parent = self; node.shared = shared; node.sharingAccount = sharingAccount; 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; [keptNodes addIndex:oldIndex]; } } if (sharingAccount) node.pithosObject.allowedTo = [NSString stringWithString:@"read"]; [newChildren addObject:node]; } } else if ([object.contentType isEqualToString:@"application/directory"]) { PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease]; node.parent = self; node.shared = shared; node.sharingAccount = sharingAccount; 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; [keptNodes addIndex:oldIndex]; } } [newChildren addObject:node]; } else { PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease]; node.parent = self; node.shared = shared; node.sharingAccount = sharingAccount; 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; [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 release]; containerRequest = nil; [objects release]; objects = nil; forcedRefresh = NO; @synchronized(self) { freshness = PithosNodeStateRefreshFinished; } // Notify observers that children are updated [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self]; } else { [containerRequest release]; // Do an additional request to fetch more objects containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name limit:0 marker:[[someObjects lastObject] name] prefix:prefix delimiter:@"/" path:nil meta:nil shared:shared until:nil] retain]; if (sharingAccount) [containerRequest setRequestUserFromDefaultTo:sharingAccount]; containerRequest.delegate = self; if (!forcedRefresh) containerRequest.downloadCache = [ASIDownloadCache sharedCache]; [containerRequest startAsynchronous]; } } - (void)containerRequestFailed:(ASIPithosContainerRequest *)request { [PithosFileUtilities httpRequestErrorAlertWithRequest:request]; [newChildren release]; newChildren = nil; [containerRequest release]; containerRequest = nil; [objects release]; objects = nil; forcedRefresh = NO; @synchronized(self) { freshness = PithosNodeStateRefreshNeeded; } } - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request { NSLog(@"URL: %@", [request url]); NSLog(@"cached: %d", [request didUseCachedResponse]); if ([request isEqualTo:applyMetadataContainerRequest]) { @synchronized(self) { [applyMetadataContainerRequest release]; applyMetadataContainerRequest = nil; } [self refreshInfo]; } else if ([request isEqualTo:refreshMetadataContainerRequest]) { [[pithosNodeInfoController window] makeFirstResponder:nil]; self.pithosContainer = [refreshMetadataContainerRequest container]; @synchronized(self) { [refreshMetadataContainerRequest release]; refreshMetadataContainerRequest = nil; } } } - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request { if ([request isEqualTo:applyMetadataContainerRequest]) { [PithosFileUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest]; @synchronized(self) { [applyMetadataContainerRequest release]; applyMetadataContainerRequest = nil; } } else if ([request isEqualTo:refreshMetadataContainerRequest]) { [PithosFileUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest]; @synchronized(self) { [refreshMetadataContainerRequest release]; refreshMetadataContainerRequest = nil; } } } #pragma mark - #pragma mark Info - (void)applyInfo { @synchronized(self) { if (applyMetadataContainerRequest == nil) { [[pithosNodeInfoController window] makeFirstResponder:nil]; applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithContainerName:pithosContainer.name policy:[NSDictionary dictionaryWithObjectsAndKeys: policyVersioning, @"versioning", [policyQuota stringValue], @"quota", nil] metadata:pithosContainer.metadata update:NO] retain]; applyMetadataContainerRequest.delegate = self; applyMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:); applyMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:); [applyMetadataContainerRequest startAsynchronous]; } } } - (void)refreshInfo { @synchronized(self) { if (refreshMetadataContainerRequest == nil) { refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithContainerName:pithosContainer.name] retain]; refreshMetadataContainerRequest.delegate = self; refreshMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:); refreshMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:); refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache]; [refreshMetadataContainerRequest startAsynchronous]; } } } #pragma mark - #pragma mark Actions - (void)showPithosNodeInfo:(id)sender { if (!pithosNodeInfoController) pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self]; [pithosNodeInfoController showWindow:sender]; [[pithosNodeInfoController window] makeKeyAndOrderFront:sender]; [NSApp activateIgnoringOtherApps:YES]; } @end