X-Git-Url: https://code.grnet.gr/git/pithos-macos/blobdiff_plain/90fadc45da65e2cc40f70560d175c699e356fe7a..258db46f261ba5b910f5441d3469f5fb64d28800:/pithos-macos/PithosAccountNode.m diff --git a/pithos-macos/PithosAccountNode.m b/pithos-macos/PithosAccountNode.m index 14d0642..42a210b 100644 --- a/pithos-macos/PithosAccountNode.m +++ b/pithos-macos/PithosAccountNode.m @@ -2,7 +2,7 @@ // PithosAccountNode.m // pithos-macos // -// Copyright 2011 GRNET S.A. All rights reserved. +// 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 @@ -37,66 +37,150 @@ #import "PithosAccountNode.h" #import "PithosContainerNode.h" +#import "ASIPithos.h" #import "ASIPithosAccountRequest.h" +#import "ASIPithosAccount.h" #import "ASIPithosContainer.h" #import "ASIDownloadCache.h" -#import "PithosFileUtilities.h" +#import "PithosAccount.h" +#import "PithosUtilities.h" +#import "PithosActivityFacility.h" static NSImage *sharedIcon = nil; @implementation PithosAccountNode +@synthesize pithos, pithosAccount, translatedGroups; + (void)initialize { if (self == [PithosAccountNode class]) - sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)] retain]; + sharedIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]; } #pragma mark - #pragma mark Object Lifecycle +- (id)initWithPithos:(ASIPithos *)aPithos { + if ((self = [super init])) { + pithos = aPithos; + } + return self; +} + - (void)dealloc { [accountRequest clearDelegatesAndCancel]; - [accountRequest release]; - [containers release]; - [super dealloc]; + [refreshMetadataAccountRequest clearDelegatesAndCancel]; + [applyMetadataAccountRequest clearDelegatesAndCancel]; +} + +#pragma mark - +#pragma mark Internal + +- (void)updateGroups { + if (!pithosAccount) { + self.translatedGroups = [NSMutableDictionary dictionary]; + } else if (pithosAccountManager) { + NSMutableSet *UUIDs = [NSMutableSet set]; + for (NSString *groupName in pithosAccount.groups) { + [UUIDs addObjectsFromArray:[pithosAccount.groups objectForKey:groupName]]; + } + [UUIDs removeObject:@""]; + [UUIDs removeObject:@"*"]; + if (UUIDs.count) { + [pithosAccountManager updateUserCatalogForForDisplaynames:nil UUIDs:[UUIDs allObjects]]; + } + + NSMutableDictionary *newTranslatedGroups = [NSMutableDictionary dictionaryWithCapacity:pithosAccount.groups.count]; + for (NSString *groupName in pithosAccount.groups) { + NSMutableArray *groupUsers = [NSMutableArray array]; + for (NSString *UUID in [pithosAccount.groups objectForKey:groupName]) { + [groupUsers addObject:[pithosAccountManager displaynameForUUID:UUID safe:YES]]; + } + [newTranslatedGroups setObject:groupUsers forKey:groupName]; + } + self.translatedGroups = newTranslatedGroups; + } else { + self.translatedGroups = [pithosAccount.groups copy]; + } } #pragma mark - #pragma mark Properties +- (void)setPithos:(ASIPithos *)aPithos { + if (aPithos && ![aPithos isEqualTo:pithos]) { + pithos = aPithos; + url = nil; + [accountRequest clearDelegatesAndCancel]; + accountRequest = nil; + [refreshMetadataAccountRequest clearDelegatesAndCancel]; + refreshMetadataAccountRequest = nil; + [applyMetadataAccountRequest clearDelegatesAndCancel]; + applyMetadataAccountRequest = nil; + reset = YES; + } +} + +- (void)setPithosAccount:(ASIPithosAccount *)aPithosAccount { + if (![pithosAccount isEqualTo:aPithosAccount]) { + pithosAccount = aPithosAccount; + [self updateGroups]; + } +} + - (NSString *)url { if (url == nil) url = [[NSString alloc] initWithFormat:@"%@%@", - (sharingAccount ? [ASIPithosRequest storageURLWithAuthUser:sharingAccount] : [ASIPithosRequest storageURL]), + (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), (shared ? @"?shared" : @"")]; return url; } - (NSArray *)children { @synchronized(self) { + if (reset) { + [accountRequest clearDelegatesAndCancel]; + accountRequest = nil; + [refreshMetadataAccountRequest clearDelegatesAndCancel]; + refreshMetadataAccountRequest = nil; + [applyMetadataAccountRequest clearDelegatesAndCancel]; + applyMetadataAccountRequest = nil; + children = nil; + newChildren = nil; + self.pithosAccount = nil; + freshness = PithosNodeStateRefreshNeeded; + forcedRefresh = YES; + reset = NO; + [self postChildrenUpdatedNotificationName]; + } switch (freshness) { case PithosNodeStateFresh: break; case PithosNodeStateRefreshNeeded: freshness = PithosNodeStateRefreshing; - accountRequest = [[ASIPithosAccountRequest listContainersRequestWithLimit:0 - marker:nil - shared:shared - until:nil] retain]; + accountRequest = [ASIPithosAccountRequest listContainersRequestWithPithos:pithos + limit:0 + marker:nil + shared:shared + until:nil]; if (sharingAccount) - [accountRequest setRequestUserFromDefaultTo:sharingAccount]; - accountRequest.delegate = self; - accountRequest.didFinishSelector = @selector(accountRequestFinished:); - accountRequest.didFailSelector = @selector(accountRequestFailed:); - if (!forcedRefresh) + [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; + else if (!forcedRefresh) accountRequest.downloadCache = [ASIDownloadCache sharedCache]; - [accountRequest startAsynchronous]; + accountRequest.delegate = self; + accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); + accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", + [NSNumber numberWithUnsignedInteger:10], @"retries", + NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", + nil]; + [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; break; case PithosNodeStateRefreshing: break; case PithosNodeStateRefreshFinished: if (newChildren) { - [children release]; children = newChildren; newChildren = nil; } @@ -109,97 +193,332 @@ static NSImage *sharedIcon = nil; } - (NSString *)displayName { - if (displayName == nil) - return [NSString stringWithString:(sharingAccount ? sharingAccount : @"account")]; - return [[displayName copy] autorelease]; + if (displayName == nil) { + if (!sharingAccount) { + return @"account"; + } else if (pithosAccountManager) { + return [pithosAccountManager displaynameForUUID:sharingAccount safe:YES]; + } else { + return [sharingAccount copy]; + } + } + return [displayName copy]; } - (NSImage *)icon { if (icon == nil) - icon = [sharedIcon retain]; + icon = sharedIcon; return icon; } #pragma mark - #pragma mark ASIHTTPRequestDelegate -- (void)accountRequestFinished:(ASIPithosAccountRequest *)request { - NSLog(@"URL: %@", [accountRequest url]); - NSLog(@"cached: %d", [accountRequest didUseCachedResponse]); - - NSArray *someContainers = [accountRequest containers]; - if (containers == nil) { - containers = [[NSMutableArray alloc] initWithArray:someContainers]; - } else { - [containers addObjectsFromArray:someContainers]; +- (void)accountRequestFailed:(ASIPithosAccountRequest *)request { + @autoreleasepool { + NSString *message; + NSError *error = [accountRequest error]; + if (error) + message = [NSString stringWithFormat:@"Account listing %@ failed: %@", accountRequest.url, [error localizedDescription]]; + else + message = [NSString stringWithFormat:@"Account listing %@ failed: (%d) %@", + accountRequest.url, accountRequest.responseStatusCode, accountRequest.responseStatusMessage]; + dispatch_async(dispatch_get_main_queue(), ^{ + [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message]; + }); + NSUInteger retries = [[accountRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue]; + if (retries > 0) { + ASIPithosAccountRequest *newAccountRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:accountRequest]; + [(NSMutableDictionary *)(newAccountRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"]; + accountRequest = newAccountRequest; + [[PithosUtilities prepareRequest:accountRequest priority:[[accountRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous]; + } else { + newChildren = nil; + accountRequest = nil; + forcedRefresh = NO; + @synchronized(self) { + freshness = PithosNodeStateRefreshNeeded; + } + } } - if ([someContainers count] < 10000) { - if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) { - // Save new children - NSLog(@"using newChildren"); - newChildren = [[NSMutableArray alloc] init]; - NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet]; - for (ASIPithosContainer *container in containers) { - PithosContainerNode *node = [[[PithosContainerNode alloc] initWithPithosContainer:container] 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 = container; - [keptNodes addIndex:oldIndex]; +} + +- (void)accountRequestFinished:(ASIPithosAccountRequest *)request { + @autoreleasepool { + DLog(@"List account finished: %@", [accountRequest url]); + DLog(@"Cached: %d", [accountRequest didUseCachedResponse]); + if (accountRequest.responseStatusCode == 200) { + self.pithosAccount = [accountRequest account]; + + NSMutableArray *containers = [accountRequest.userInfo objectForKey:@"containers"]; + NSArray *someContainers = [accountRequest containers]; + if (containers == nil) { + containers = [NSMutableArray arrayWithArray:someContainers]; + } else { + [containers addObjectsFromArray:someContainers]; + } + if ([someContainers count] < 10000) { + if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) { + // Save new children + DLog(@"using newChildren"); + newChildren = [[NSMutableArray alloc] init]; + NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet]; + for (ASIPithosContainer *container in containers) { + PithosContainerNode *node = [[PithosContainerNode alloc] initWithPithos:pithos pithosContainer:container]; + 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 = container; + [node setLimitedPithosContainer:container]; + [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)]; } - [newChildren addObject:node]; + // Else cache was used and all results were fetched during this request, so existing children can be reused + accountRequest = nil; + forcedRefresh = NO; + @synchronized(self) { + freshness = PithosNodeStateRefreshFinished; + } + [self postChildrenUpdatedNotificationName]; + } else { + // Do an additional request to fetch more objects + accountRequest = [ASIPithosAccountRequest listContainersRequestWithPithos:pithos + limit:0 + marker:[[someContainers lastObject] name] + shared:shared + until:nil]; + if (sharingAccount) + [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; + else if (!forcedRefresh) + accountRequest.downloadCache = [ASIDownloadCache sharedCache]; + accountRequest.delegate = self; + accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); + accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", + [NSNumber numberWithUnsignedInteger:10], @"retries", + NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", + containers, @"containers", + nil]; + [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous]; + } + } else if (accountRequest.responseStatusCode == 304) { + // Account is not modified, so existing children can be reused + accountRequest = nil; + forcedRefresh = NO; + @synchronized(self) { + freshness = PithosNodeStateRefreshFinished; } - [[children objectsAtIndexes: - [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){ - if ([keptNodes containsIndex:idx]) - return NO; - return YES; - }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)]; + [self postChildrenUpdatedNotificationName]; + } else { + [self accountRequestFailed:accountRequest]; } - // Else cache was used and all results were fetched during this request, so existing children can be reused - [accountRequest release]; - accountRequest = nil; - [containers release]; - containers = nil; - forcedRefresh = NO; - @synchronized(self) { - freshness = PithosNodeStateRefreshFinished; + } +} + +- (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request { + @autoreleasepool { + DLog(@"URL: %@", [request url]); + DLog(@"cached: %d", [request didUseCachedResponse]); + + if ([request isEqualTo:applyMetadataAccountRequest]) { + @synchronized(self) { + applyMetadataAccountRequest = nil; + } + [self refreshInfo]; + } else if ([request isEqualTo:refreshMetadataAccountRequest]) { + self.pithosAccount = [refreshMetadataAccountRequest account]; + @synchronized(self) { + refreshMetadataAccountRequest = nil; + } } - // Notify observers that children are updated - [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosAccountNodeChildrenUpdated" object:self]; - } else { - [accountRequest release]; - // Do an additional request to fetch more objects - accountRequest = [[ASIPithosAccountRequest listContainersRequestWithLimit:0 - marker:[[someContainers lastObject] name] - shared:shared - until:nil] retain]; - if (sharingAccount) - [accountRequest setRequestUserFromDefaultTo:sharingAccount]; - accountRequest.delegate = self; - if (!forcedRefresh) - accountRequest.downloadCache = [ASIDownloadCache sharedCache]; - [accountRequest startAsynchronous]; } } -- (void)accountRequestFailed:(ASIPithosAccountRequest *)request { - [PithosFileUtilities httpRequestErrorAlertWithRequest:request]; - [newChildren release]; - newChildren = nil; - [accountRequest release]; - accountRequest = nil; - [containers release]; - containers = nil; - forcedRefresh = NO; +- (void)accountMetadataRequestFailed:(ASIPithosAccountRequest *)request { + @autoreleasepool { + NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue]; + if (retries > 0) { + ASIPithosAccountRequest *newRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:request]; + [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"]; + if ([request isEqualTo:applyMetadataAccountRequest]) { + @synchronized(self) { + applyMetadataAccountRequest = newRequest; + } + } else if ([request isEqualTo:refreshMetadataAccountRequest]) { + @synchronized(self) { + refreshMetadataAccountRequest = newRequest; + } + } + [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous]; + } else { + if ([request isEqualTo:applyMetadataAccountRequest]) { + [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataAccountRequest]; + @synchronized(self) { + applyMetadataAccountRequest = nil; + } + } else if ([request isEqualTo:refreshMetadataAccountRequest]) { + [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataAccountRequest]; + @synchronized(self) { + refreshMetadataAccountRequest = nil; + } + } + } + } +} + +#pragma mark - +#pragma mark Info + +- (void)applyInfo { @synchronized(self) { - freshness = PithosNodeStateRefreshNeeded; + if (applyMetadataAccountRequest == nil) { + NSMutableDictionary *groups = [NSMutableDictionary dictionary]; + if (translatedGroups.count) { + for (NSString *groupName in translatedGroups) { + if (!groupName.length || + [groupName rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound) { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Invalid Input"]; + [alert setInformativeText:@"Group names cannot be empty or contain ' ', '-', '_', '~', ',' or ';'."]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + return; + } + } + if (pithosAccountManager) { + NSMutableSet *allGroupUsers = [NSMutableSet set]; + for (NSMutableArray *groupUsers in [translatedGroups objectEnumerator]) { + [allGroupUsers addObjectsFromArray:groupUsers]; + } + [allGroupUsers removeObject:@""]; + [allGroupUsers removeObject:@"*"]; + if (allGroupUsers.count) { + ASIPithosRequest *userCatalogRequest = [pithosAccountManager updateUserCatalogForForDisplaynames:[allGroupUsers allObjects] + UUIDs:nil]; + if (userCatalogRequest.error || ((userCatalogRequest.responseStatusCode != 200) && (userCatalogRequest.responseStatusCode != 404))) { + return; + } else if (userCatalogRequest.responseStatusCode == 200) { + // Check if all users exist. + NSDictionary *displaynameCatalog = [userCatalogRequest displaynameCatalog]; + NSMutableArray *inexistentGroupUsers = [NSMutableArray array]; + for (NSString *groupUser in allGroupUsers) { + if (![displaynameCatalog objectForKey:groupUser]) { + [inexistentGroupUsers addObject:groupUser]; + } + } + if (!inexistentGroupUsers.count) { + // Create groups. + for (NSString *groupName in translatedGroups) { + NSMutableArray *groupUsers = [NSMutableArray array]; + for (NSString *groupUser in [translatedGroups objectForKey:groupName]) { + [groupUsers addObject:([groupUser isEqualToString:@"*"] ? + @"*" : [displaynameCatalog objectForKey:groupUser])]; + } + [groups setObject:groupUsers forKey:groupName]; + } + } else { + NSAlert *alert = [[NSAlert alloc] init]; + if (inexistentGroupUsers.count == 1) { + [alert setMessageText:@"Invalid User"]; + [alert setInformativeText:[NSString stringWithFormat:@"User '%@' doesn't exist.", [inexistentGroupUsers objectAtIndex:0]]]; + } else { + [alert setMessageText:@"Invalid Users"]; + [alert setInformativeText:[NSString stringWithFormat:@"Users '%@' don't exist.", [inexistentGroupUsers componentsJoinedByString:@"', '"]]]; + } + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + return; + } + } else { + // 404. Since we don't translate to UUIDs, check for invalid chars. + BOOL valid = YES; + for (NSString *groupUser in allGroupUsers) { + if ([groupUser rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" ~,;:"]].location != NSNotFound) { + valid = NO; + break; + } + } + if (valid) { + [groups addEntriesFromDictionary:translatedGroups]; + } else { + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:@"Invalid Input"]; + [alert setInformativeText:@"Users cannot contain ' ', '~', ',', ';' or ':'."]; + [alert addButtonWithTitle:@"OK"]; + [alert runModal]; + return; + } + } + } else { + for (NSString *groupName in translatedGroups) { + if ([[translatedGroups objectForKey:groupName] containsObject:@"*"]) { + [groups setObject:[NSMutableArray arrayWithObject:@"*"] forKey:groupName]; + } + } + if (!groups.count) { + [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"]; + } + } + } else { + [groups addEntriesFromDictionary:translatedGroups]; + } + } else { + [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"]; + } + + applyMetadataAccountRequest = [ASIPithosAccountRequest updateAccountMetadataRequestWithPithos:pithos + groups:groups + metadata:pithosAccount.metadata + update:NO]; + applyMetadataAccountRequest.delegate = self; + applyMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); + applyMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + applyMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", + [NSNumber numberWithUnsignedInteger:10], @"retries", + NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", + nil]; + [[PithosUtilities prepareRequest:applyMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous]; + } + } +} + +- (void)refreshInfo { + @synchronized(self) { + if (refreshMetadataAccountRequest == nil) { + refreshMetadataAccountRequest = [ASIPithosAccountRequest accountMetadataRequestWithPithos:pithos]; + refreshMetadataAccountRequest.delegate = self; + refreshMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); + refreshMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); + refreshMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", + [NSNumber numberWithUnsignedInteger:10], @"retries", + NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", + NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", + nil]; + if (!sharingAccount) + refreshMetadataAccountRequest.downloadCache = [ASIDownloadCache sharedCache]; + [[PithosUtilities prepareRequest:refreshMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous]; + } } }