Improve UUID translation in nodes
[pithos-macos] / pithos-macos / PithosSubdirNode.m
index 058c996..7c3d513 100644 (file)
@@ -1,8 +1,8 @@
 //
-//  PithosNode.m
+//  PithosSubdirNode.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
 
 #import "PithosSubdirNode.h"
 #import "ASIPithosRequest.h"
+#import "ASIPithos.h"
+#import "ASIPithosObjectRequest.h"
+#import "ASIPithosContainer.h"
+#import "ASIPithosObject.h"
+#import "ASIPithosSharingUser.h"
+#import "ASINetworkQueue.h"
+#import "ASIDownloadCache.h"
+#import "PithosAccount.h"
+#import "PithosUtilities.h"
+#import "PithosObjectNodeInfoController.h"
 
-static NSImage *classIcon = nil;
+static NSImage *sharedIcon = nil;
 
 @implementation PithosSubdirNode
+@synthesize pithosObject, versions, applyMetadataObjectRequest, refreshMetadataObjectRequest, refreshVersionsObjectRequest;
+@synthesize isPublic, translatedModifiedBy, translatedPermissions;
 
 + (void)initialize {
        if (self == [PithosSubdirNode class])
-        classIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)] retain];
+        sharedIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericFolderIcon)];
 }
 
 #pragma mark -
 #pragma mark Object Lifecycle
 
-- (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer pithosObject:(ASIPithosObject *)aPithosObject {
-    if ((self = [super init])) {
-        pithosContainer = [aPithosContainer retain];
-        pithosObject = [aPithosObject retain];
-        refreshing = NO;
-        prefix = [[pithosObject.name substringToIndex:([pithosObject.name length] - 1)] retain];
+- (id)initWithPithosAccountManager:(PithosAccount *)aPithosAccountManager
+                         andPithos:(ASIPithos *)aPithos
+                   pithosContainer:(ASIPithosContainer *)aPithosContainer
+                      pithosObject:(ASIPithosObject *)aPithosObject {
+    if ((self = [super initWithPithosAccountManager:aPithosAccountManager])) {
+        pithos = aPithos;
+        refreshParent = NO;
+        self.pithosContainer = aPithosContainer;
+        self.pithosObject = aPithosObject;
     }
     return self;
 }
 
 - (void)dealloc {
-    [pithosObject release];
-    [super 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 updateUserCatalogForForDisplaynames: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 updateUserCatalogForForDisplaynames: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
 
+- (void)setPithos:(ASIPithos *)aPithos {
+    if (aPithos && ![aPithos isEqualTo:pithos]) {
+        pithos = aPithos;
+        url = nil;
+    }
+}
+
 - (NSString *)url {
-    if (url == nil) 
-        url = [[NSString alloc] initWithFormat:@"subdir %@/%@/%@", [ASIPithosRequest storageURL], pithosContainer.name, pithosObject.name];
+    if (url == nil)
+        url = [[NSString alloc] initWithFormat:@"subdir %@/%@/%@%@", 
+               (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
+               pithosContainer.name, 
+               prefix, 
+               (shared ? @"?shared" : @"")];
     return url;
 }
 
 - (NSString *)displayName {
-    return [pithosObject.name lastPathComponent];
+    if (displayName == nil) {
+        displayName = [pithosObject.name lastPathComponent];
+        if (!pithosObject.subdir && [pithosObject.name hasSuffix:@"/"])
+            displayName = [displayName stringByAppendingString:@"/"];
+    } 
+    return [displayName copy];
+}
+
+- (void)setDisplayName:(NSString *)aDisplayName {    
 }
 
 - (NSImage *)icon {
-    if (icon)
-        return icon;
-    return classIcon;
+    if (icon == nil)
+        icon = sharedIcon;
+    return icon;
+}
+
+- (void)setPithosObject:(ASIPithosObject *)aPithosObject {
+    if (![pithosObject isEqualTo:aPithosObject]) {
+        pithosObject = aPithosObject;
+        [self updateModifiedBy];
+        [self updatePermissions];
+    }
+    if (pithosObject.subdir) {
+        self.prefix = [pithosObject.name substringToIndex:([pithosObject.name length] - 1)];
+    } else {
+        self.prefix = [NSString stringWithString:pithosObject.name];
+    }
+    self.isPublic = (pithosObject.publicURI != nil);
+    // Refresh browser if the object is in my shared and is no longer shared
+    if (shared && !pithosObject.subdir && !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 != 201) && (responseStatusCode != 202))
+                [PithosUtilities unexpectedResponseStatusAlertWithRequest:applyMetadataObjectRequest];
+            @synchronized(self) {
+                self.applyMetadataObjectRequest = nil;
+            }
+            if ((responseStatusCode == 201) || (responseStatusCode == 202))
+                [self refreshInfo];
+        } else if ([request isEqualTo:refreshMetadataObjectRequest]) {
+            [[pithosNodeInfoController window] makeFirstResponder:nil];
+            self.pithosObject = [refreshMetadataObjectRequest object];
+            if (refreshParent) {
+                // Ask the parent for refresh for the case where an object was removed
+                // It is done here so that it doesn't affect the info window refresh
+                [parent refresh];
+                refreshParent = NO;
+            }
+            @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 copyRequest:request];
+            [(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) {
+            if (pithosObject.subdir) {
+                BOOL createObject = NO;
+                NSAlert *alert;
+                ASIPithosObjectRequest *request = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos 
+                                                                                            containerName:pithosContainer.name 
+                                                                                               objectName:prefix];
+                [PithosUtilities startAndWaitForRequest:request];
+                if ([request error]) {
+                    [PithosUtilities httpRequestErrorAlertWithRequest:request];
+                    return;
+                } else if (request.responseStatusCode == 200) {
+                    alert = [[NSAlert alloc] init];
+                    [alert setMessageText:@"Apply changes"];
+                    [alert setInformativeText:[NSString stringWithFormat:@"In order to apply the changes in '%@', the object at the same path must be replaced. Continue?", self.displayName]];
+                    [alert addButtonWithTitle:@"OK"];
+                    [alert addButtonWithTitle:@"Cancel"];
+                    NSInteger choice = [alert runModal];
+                    if (choice == NSAlertFirstButtonReturn) {
+                        request = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos 
+                                                                          containerName:pithosContainer.name 
+                                                                             objectName:prefix];
+                        [PithosUtilities startAndWaitForRequest:request];
+                        if ([request error]) {
+                            [PithosUtilities httpRequestErrorAlertWithRequest:request];
+                            return;
+                        } else if (request.responseStatusCode != 204) {
+                            [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
+                            return;
+                        }
+                        refreshParent = YES;
+                        createObject = YES;
+                    } else {
+                        return;
+                    }
+                } else if (request.responseStatusCode == 404) {
+                    createObject = YES;
+                } else {
+                    [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
+                    return;
+                }
+                if (createObject) {
+                    [[pithosNodeInfoController window] makeFirstResponder:nil];
+                    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 updateUserCatalogForForDisplaynames:[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 writeObjectDataRequestWithPithos:pithos
+                                                                                                 containerName:pithosContainer.name
+                                                                                                    objectName:prefix
+                                                                                                          eTag:nil
+                                                                                                   contentType:@"application/directory"
+                                                                                               contentEncoding:pithosObject.contentEncoding
+                                                                                            contentDisposition:pithosObject.contentDisposition
+                                                                                                      manifest:pithosObject.manifest
+                                                                                                       sharing:(pithosObject.sharing ? pithosObject.sharing : @"")
+                                                                                                      isPublic:(isPublic ? ASIPithosObjectRequestPublicTrue : ASIPithosObjectRequestPublicFalse)
+                                                                                                      metadata:pithosObject.metadata
+                                                                                                          data:[NSData data]];
+                    pithosObject.subdir = 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];
+                }
+            } else {
+                [[pithosNodeInfoController window] makeFirstResponder:nil];
+                if (sharingAccount) {
+                    self.applyMetadataObjectRequest = [ASIPithosObjectRequest updateObjectMetadataRequestWithPithos: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: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 updateUserCatalogForForDisplaynames:[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: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 (pithosObject.subdir) {
+            self.pithosObject = [ASIPithosObject subdirWithName:pithosObject.name];
+            return;
+        } else if (refreshMetadataObjectRequest == nil) {
+            self.refreshMetadataObjectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos
+                                                                                          containerName:pithosContainer.name
+                                                                                             objectName:prefix];
+            if (sharingAccount)
+                [refreshMetadataObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos: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 (pithosObject.subdir) {
+            return;
+        } else if (refreshVersionsObjectRequest == nil) {
+            self.refreshVersionsObjectRequest = [ASIPithosObjectRequest objectVersionsRequestWithPithos:pithos
+                                                                                          containerName:pithosContainer.name
+                                                                                             objectName:pithosObject.name];
+            if (sharingAccount)
+                [refreshVersionsObjectRequest setRequestUserFromDefaultTo:sharingAccount withPithos: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