// // PithosObjectNode.m // pithos-macos // // Copyright 2011-2013 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 "PithosObjectNode.h" #import "ASIPithosRequest.h" #import "ASIPithos.h" #import "ASIPithosObjectRequest.h" #import "ASIPithosContainer.h" #import "ASIPithosObject.h" #import "ASIPithosSharingUser.h" #import "ASIDownloadCache.h" #import "PithosAccount.h" #import "PithosUtilities.h" #import "PithosObjectNodeInfoController.h" @implementation PithosObjectNode @synthesize pithosContainer, pithosObject, versions, applyMetadataObjectRequest, refreshMetadataObjectRequest, refreshVersionsObjectRequest; @synthesize isPublic, translatedModifiedBy, translatedPermissions; #pragma mark - #pragma mark Object Lifecycle - (id)initWithPithosAccountManager:(PithosAccount *)aPithosAccountManager pithosContainer:(ASIPithosContainer *)aPithosContainer pithosObject:(ASIPithosObject *)aPithosObject { if ((self = [super initWithPithosAccountManager:aPithosAccountManager])) { isLeafItem = YES; self.pithosContainer = aPithosContainer; self.pithosObject = aPithosObject; } return self; } - (void)dealloc { [refreshVersionsObjectRequest clearDelegatesAndCancel]; [refreshMetadataObjectRequest clearDelegatesAndCancel]; [applyMetadataObjectRequest clearDelegatesAndCancel]; } #pragma mark - #pragma mark Internal - (void)updateModifiedBy { if (!pithosObject.modifiedBy) { self.translatedModifiedBy = nil; } else if (pithosAccountManager) { NSString *displayname = [pithosAccountManager displaynameForUUID:pithosObject.modifiedBy safe:NO]; if (displayname) { self.translatedModifiedBy = displayname; } else { [pithosAccountManager updateUserCatalogForDisplaynames:nil UUIDs:[NSArray arrayWithObject:pithosObject.modifiedBy]]; self.translatedModifiedBy = [pithosAccountManager displaynameForUUID:pithosObject.modifiedBy safe:YES]; } } else { self.translatedModifiedBy = [pithosObject.modifiedBy copy]; } } - (void)updatePermissions { if (!pithosObject) { self.translatedPermissions = [NSMutableArray array]; } else if (pithosAccountManager) { NSMutableSet *UUIDs = [NSMutableSet set]; for (ASIPithosSharingUser *sharingUser in pithosObject.permissions) { [UUIDs addObject:sharingUser.name]; } [UUIDs removeObject:@""]; [UUIDs removeObject:@"*"]; if (UUIDs.count) { [pithosAccountManager updateUserCatalogForDisplaynames:nil UUIDs:[UUIDs allObjects]]; } NSMutableArray *newTranslatedPermissions = [NSMutableArray arrayWithCapacity:pithosObject.permissions.count]; for (ASIPithosSharingUser *sharingUser in pithosObject.permissions) { ASIPithosSharingUser *translatedSharingUser = [sharingUser copy]; translatedSharingUser.name = [pithosAccountManager displaynameForUUID:translatedSharingUser.name safe:YES]; [newTranslatedPermissions addObject:translatedSharingUser]; } self.translatedPermissions = newTranslatedPermissions; } else { self.translatedPermissions = [NSMutableArray arrayWithArray:[pithosObject.permissions copy]]; } } #pragma mark - #pragma mark Properties - (NSString *)url { return [NSString stringWithFormat:@"@object@%@/%@/%@%@", (sharingAccount ? sharingAccount : pithosAccountManager.pithos.authUser), pithosContainer.name, pithosObject.name, (shared ? @"?shared" : @"")]; } - (NSArray *)children { return nil; } - (NSString *)displayName { if (displayName == nil) { displayName = [pithosObject.name lastPathComponent]; if([pithosObject.name hasSuffix:@"/"]) displayName = [displayName stringByAppendingString:@"/"]; } return [displayName copy]; } - (void)setDisplayName:(NSString *)aDisplayName { } - (NSImage *)icon { if (icon == nil) icon = [[NSWorkspace sharedWorkspace] iconForFileType:[pithosObject.name pathExtension]]; return icon; } - (void)setPithosObject:(ASIPithosObject *)aPithosObject { if (![pithosObject isEqualTo:aPithosObject]) { pithosObject = aPithosObject; [self updateModifiedBy]; [self updatePermissions]; } self.isPublic = (pithosObject.publicURI != nil); // Refresh browser if the object is in my shared and is no longer shared if (shared && !pithosObject.sharing) [[NSNotificationCenter defaultCenter] postNotificationName:@"PithosBrowserRefreshNeeded" object:self]; } - (void)setLimitedPithosObject:(ASIPithosObject *)aPithosObject { if (![pithosObject isEqualTo:aPithosObject]) { self.pithosObject.subdir = aPithosObject.subdir; self.pithosObject.name = aPithosObject.name; self.pithosObject.hash = aPithosObject.hash; self.pithosObject.objectHash = aPithosObject.objectHash; self.pithosObject.UUID = aPithosObject.UUID; self.pithosObject.bytes = aPithosObject.bytes; self.pithosObject.contentType = aPithosObject.contentType; self.pithosObject.lastModified = aPithosObject.lastModified; self.pithosObject.version = aPithosObject.version; self.pithosObject.versionTimestamp = aPithosObject.versionTimestamp; self.pithosObject.modifiedBy = aPithosObject.modifiedBy; self.pithosObject.sharedBy = aPithosObject.sharedBy; self.pithosObject.allowedTo = aPithosObject.allowedTo; if (!pithosNodeInfoController) { self.pithosObject.sharing = aPithosObject.sharing; self.pithosObject.publicURI = aPithosObject.publicURI; self.pithosObject = pithosObject; [self updatePermissions]; } else { [self updateModifiedBy]; } } } #pragma mark - #pragma mark ASIHTTPRequestDelegate - (void)objectRequestFinished:(ASIPithosObjectRequest *)request { @autoreleasepool { DLog(@"URL: %@", [request url]); DLog(@"cached: %d", [request didUseCachedResponse]); if ([request isEqualTo:applyMetadataObjectRequest]) { int responseStatusCode = applyMetadataObjectRequest.responseStatusCode; if (responseStatusCode != 202) [PithosUtilities unexpectedResponseStatusAlertWithRequest:applyMetadataObjectRequest]; @synchronized(self) { self.applyMetadataObjectRequest = nil; } if (responseStatusCode == 202) [self refreshInfo]; } else if ([request isEqualTo:refreshMetadataObjectRequest]) { [[pithosNodeInfoController window] makeFirstResponder:nil]; self.pithosObject = [refreshMetadataObjectRequest object]; @synchronized(self) { self.refreshMetadataObjectRequest = nil; } } else if ([request isEqualTo:refreshVersionsObjectRequest]) { [[pithosNodeInfoController window] makeFirstResponder:nil]; self.versions = [refreshVersionsObjectRequest versions]; @synchronized(self) { self.refreshVersionsObjectRequest = nil; } } } } - (void)objectRequestFailed:(ASIPithosObjectRequest *)request { @autoreleasepool { NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue]; if (retries > 0) { ASIPithosObjectRequest *newRequest = (ASIPithosObjectRequest *)[PithosUtilities retryWithUpdatedURLRequest:request andPithosAccountManager:pithosAccountManager]; [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"]; if ([request isEqualTo:applyMetadataObjectRequest]) { @synchronized(self) { self.applyMetadataObjectRequest = newRequest; } } else if ([request isEqualTo:refreshMetadataObjectRequest]) { @synchronized(self) { self.refreshMetadataObjectRequest = newRequest; } } else if ([request isEqualTo:refreshVersionsObjectRequest]) { @synchronized(self) { self.refreshVersionsObjectRequest = newRequest; } } [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous]; } else { if ([request isEqualTo:applyMetadataObjectRequest]) { [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataObjectRequest]; @synchronized(self) { self.applyMetadataObjectRequest = nil; } } else if ([request isEqualTo:refreshMetadataObjectRequest]) { [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataObjectRequest]; @synchronized(self) { self.refreshMetadataObjectRequest = nil; } } else if ([request isEqualTo:refreshVersionsObjectRequest]) { [PithosUtilities httpRequestErrorAlertWithRequest:refreshVersionsObjectRequest]; @synchronized(self) { self.refreshVersionsObjectRequest = nil; } } } } } #pragma mark - #pragma mark Info - (void)applyInfo { @synchronized(self) { if (applyMetadataObjectRequest == nil) { [[pithosNodeInfoController window] makeFirstResponder:nil]; if (sharingAccount) { self.applyMetadataObjectRequest = [ASIPithosObjectRequest updateObjectMetadataRequestWithPithos:pithosAccountManager.pithos containerName:pithosContainer.name objectName:pithosObject.name contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) metadata:pithosObject.metadata update:NO]; [applyMetadataObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithosAccountManager.pithos]; } else { NSMutableArray *permissions = [NSMutableArray array]; if (translatedPermissions.count) { for (ASIPithosSharingUser *translatedsSharingUser in translatedPermissions) { if (translatedsSharingUser.group.length && [translatedsSharingUser.group rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Invalid Input"]; [alert setInformativeText:@"Group names cannot contain ' ', '-', '_', '~', ',' or ';'."]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return; } } if (pithosAccountManager) { NSMutableSet *allUsers = [NSMutableSet set]; for (ASIPithosSharingUser *translatedsSharingUser in translatedPermissions) { if (translatedsSharingUser.name.length) { [allUsers addObject:translatedsSharingUser.name]; } } [allUsers removeObject:@""]; [allUsers removeObject:@"*"]; if (allUsers.count) { ASIPithosRequest *userCatalogRequest = [pithosAccountManager updateUserCatalogForDisplaynames:[allUsers 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 *inexistentUsers = [NSMutableArray array]; for (NSString *user in allUsers) { if (![displaynameCatalog objectForKey:user]) { [inexistentUsers addObject:user]; } } if (!inexistentUsers.count) { // Create permissions. for (ASIPithosSharingUser *translatedsSharingUser in translatedPermissions) { if (translatedsSharingUser.name.length) { ASIPithosSharingUser *sharingUser = [translatedsSharingUser copy]; if (![sharingUser.name isEqualToString:@"*"]) { sharingUser.name = [displaynameCatalog objectForKey:sharingUser.name]; } if (!sharingUser.permission) { sharingUser.permission = @"read"; } [permissions addObject:sharingUser]; } } } else { NSAlert *alert = [[NSAlert alloc] init]; if (inexistentUsers.count == 1) { [alert setMessageText:@"Invalid User"]; [alert setInformativeText:[NSString stringWithFormat:@"User '%@' doesn't exist.", [inexistentUsers objectAtIndex:0]]]; } else { [alert setMessageText:@"Invalid Users"]; [alert setInformativeText:[NSString stringWithFormat:@"Users '%@' don't exist.", [inexistentUsers componentsJoinedByString:@"', '"]]]; } [alert addButtonWithTitle:@"OK"]; [alert runModal]; return; } } else { // 404. Since we don't translate to UUIDs, check for invalid chars. BOOL valid = YES; // Create permissions. for (ASIPithosSharingUser *translatedsSharingUser in translatedPermissions) { if (translatedsSharingUser.name.length && ([translatedsSharingUser.name rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" ~,;:"]].location != NSNotFound)) { valid = NO; break; } } if (valid) { for (ASIPithosSharingUser *translatedsSharingUser in translatedPermissions) { if (translatedsSharingUser.name.length) { ASIPithosSharingUser *sharingUser = [translatedsSharingUser copy]; if (!sharingUser.permission) { sharingUser.permission = @"read"; } [permissions addObject:sharingUser]; } } } else { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:@"Invalid Input"]; [alert setInformativeText:@"Users cannot contain ' ', '~', ',', ';' or ':'."]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return; } } } else { for (ASIPithosSharingUser *translatedsSharingUser in translatedPermissions) { if ([translatedsSharingUser.name isEqualToString:@"*"]) { ASIPithosSharingUser *sharingUser = [translatedsSharingUser copy]; if (!sharingUser.permission) { sharingUser.permission = @"read"; } [permissions addObject:sharingUser]; } } } } else { for (ASIPithosSharingUser *translatedsSharingUser in translatedPermissions) { if (translatedsSharingUser.name.length) { ASIPithosSharingUser *sharingUser = [translatedsSharingUser copy]; if (!sharingUser.permission) { sharingUser.permission = @"read"; } [permissions addObject:sharingUser]; } } } } pithosObject.permissions = permissions; self.applyMetadataObjectRequest = [ASIPithosObjectRequest updateObjectMetadataRequestWithPithos:pithosAccountManager.pithos containerName:pithosContainer.name objectName:pithosObject.name contentEncoding:pithosObject.contentEncoding contentDisposition:pithosObject.contentDisposition manifest:pithosObject.manifest sharing:(pithosObject.sharing ? pithosObject.sharing : @"") isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse) metadata:pithosObject.metadata update:NO]; } applyMetadataObjectRequest.delegate = self; applyMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); applyMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); applyMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", nil]; [[PithosUtilities prepareRequest:applyMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous]; } } } - (void)refreshInfo { @synchronized(self) { if (refreshMetadataObjectRequest == nil) { self.refreshMetadataObjectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithosAccountManager.pithos containerName:pithosContainer.name objectName:pithosObject.name]; if (sharingAccount) [refreshMetadataObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithosAccountManager.pithos]; refreshMetadataObjectRequest.delegate = self; refreshMetadataObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); refreshMetadataObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); refreshMetadataObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", nil]; if (!sharingAccount) refreshMetadataObjectRequest.downloadCache = [ASIDownloadCache sharedCache]; [[PithosUtilities prepareRequest:refreshMetadataObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous]; } } [self refreshVersions]; } #pragma mark - #pragma mark Versions - (void)refreshVersions { @synchronized(self) { if (refreshVersionsObjectRequest == nil) { self.refreshVersionsObjectRequest = [ASIPithosObjectRequest objectVersionsRequestWithPithos:pithosAccountManager.pithos containerName:pithosContainer.name objectName:pithosObject.name]; if (sharingAccount) [refreshVersionsObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithosAccountManager.pithos]; refreshVersionsObjectRequest.delegate = self; refreshVersionsObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:); refreshVersionsObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:); refreshVersionsObjectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", [NSNumber numberWithUnsignedInteger:10], @"retries", NSStringFromSelector(@selector(objectRequestFinished:)), @"didFinishSelector", NSStringFromSelector(@selector(objectRequestFailed:)), @"didFailSelector", nil]; if (!sharingAccount) refreshVersionsObjectRequest.downloadCache = [ASIDownloadCache sharedCache]; [[PithosUtilities prepareRequest:refreshVersionsObjectRequest priority:NSOperationQueuePriorityHigh] startAsynchronous]; } } } #pragma mark - #pragma mark Actions - (void)showPithosNodeInfo:(id)sender { if (!pithosNodeInfoController) { pithosNodeInfoController = [[PithosObjectNodeInfoController alloc] initWithPithosNode:self]; [self refreshInfo]; } [pithosNodeInfoController showWindow:sender]; [[pithosNodeInfoController window] makeKeyAndOrderFront:sender]; [NSApp activateIgnoringOtherApps:YES]; } @end