Improve code and fix bugs
[pithos-macos] / pithos-macos / PithosAccountNode.m
index 14d0642..42a210b 100644 (file)
@@ -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
 
 #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];
+        }
     }
 }