Merge branch 'develop'
authorMiltiadis Vasilakis <mvasilak@gmail.com>
Sat, 9 Feb 2013 14:52:06 +0000 (16:52 +0200)
committerMiltiadis Vasilakis <mvasilak@gmail.com>
Sat, 9 Feb 2013 15:01:14 +0000 (17:01 +0200)
61 files changed:
Classes/AccountDetailsViewController.m
Classes/AccountGroupsViewController.h
Classes/AccountGroupsViewController.m
Classes/AccountHomeViewController.m
Classes/AccountManager.h
Classes/AccountManager.m
Classes/AccountSettingsViewController.h
Classes/AccountSettingsViewController.m
Classes/ActivityIndicatorView.h
Classes/ActivityIndicatorView.m
Classes/AddContainerViewController.h
Classes/AddContainerViewController.m
Classes/AddFileViewController.h
Classes/AddFolderViewController.m
Classes/AddObjectViewController.h
Classes/AddPhotoViewController.h
Classes/AddPhotoViewController.m
Classes/ContainerDetailViewController.h
Classes/ContainerDetailViewController.m
Classes/ContainersViewController.h
Classes/ContainersViewController.m
Classes/ContainersViewController.xib
Classes/EditAccountGroupsViewController.h
Classes/EditAccountGroupsViewController.m
Classes/EditMetadataViewController.m
Classes/EditPermissionsViewController.h
Classes/EditPermissionsViewController.m
Classes/EditPolicyViewController.m
Classes/ErrorAlerter.m
Classes/Folder.m
Classes/FolderDetailViewController.h
Classes/FolderDetailViewController.m
Classes/FolderViewController.h
Classes/FolderViewController.m
Classes/FolderViewController.xib
Classes/GetContainersRequest.h [deleted file]
Classes/GetContainersRequest.m [deleted file]
Classes/GetObjectsRequest.h
Classes/GetObjectsRequest.m
Classes/ObjectVersionsViewController.m
Classes/OpenStackAccount.h
Classes/OpenStackAccount.m
Classes/OpenStackAppDelegate.m
Classes/OpenStackRequest.h
Classes/OpenStackRequest.m
Classes/OpenStackViewController.h
Classes/OpenStackViewController.m
Classes/Provider.h
Classes/Provider.m
Classes/RootViewController.m
Classes/SharingAccountsViewController.h
Classes/SharingAccountsViewController.m
Classes/SharingAccountsViewController.xib
Classes/StorageObject.h
Classes/StorageObject.m
Classes/StorageObjectViewController.h
Classes/StorageObjectViewController.m
Classes/UploadGenericFileViewController.h
Classes/UploadGenericFileViewController.m
OpenStack-Info.plist
OpenStack.xcodeproj/project.pbxproj

index e43e299..efcc50d 100755 (executable)
@@ -10,6 +10,8 @@
 #import "Provider.h"
 #import "RSTextFieldCell.h"
 #import "OpenStackAccount.h"
+#import "AccountManager.h"
+#import "APICallback.h"
 #import "RootViewController.h"
 #import "OpenStackRequest.h"
 #import "UIViewController+Conveniences.h"
     
     if (indexPath.section == authenticationSection) {
         if (indexPath.row == kUsername) {
-            cell = [self textCell:@"Username" textField:&usernameTextField secure:NO returnKeyType:UIReturnKeyNext];
+            cell = [self textCell:@"UUID" textField:&usernameTextField secure:NO returnKeyType:UIReturnKeyNext];
             if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
                 CGRect rect = usernameTextField.frame;
                 CGFloat offset = 19.0;
                 loginURLPrefix = [[NSURL URLWithString:apiEndpointTextField.text] URLByAppendingPathComponent:@"login"];
             }
         } else {
-            loginURLPrefix = [[provider.authEndpointURL URLByDeletingLastPathComponent] URLByAppendingPathComponent:@"login"];
+            loginURLPrefix = provider.loginURL;
         }
         NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
         NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", loginURLPrefix, protocol];
         self.navigationItem.rightBarButtonItem.enabled = YES;
         [apiEndpointTextField becomeFirstResponder];
     } else if (!usernameTextField.text || [usernameTextField.text isEqualToString:@""]) {
-        [self alert:nil message:@"Please enter your Username."];
+        [self alert:nil message:@"Please enter your UUID."];
         self.navigationItem.rightBarButtonItem.enabled = YES;
         [usernameTextField becomeFirstResponder];
     } else if (!authTokenTextField.text || [authTokenTextField.text isEqualToString:@""]) {
         if (customProvider) {
             account.provider = [[[Provider alloc] init] autorelease];
             account.provider.name = providerNameTextField.text;
-            account.provider.authEndpointURL = [[NSURL URLWithString:apiEndpointTextField.text] URLByAppendingPathComponent:@"v1"];
+            account.provider.hostURL = [NSURL URLWithString:apiEndpointTextField.text];
+            account.provider.version = @"v1";
         } else {
             account.provider = provider;
         }
         account.username = usernameTextField.text;
         account.authToken = authTokenTextField.text;
-        account.hostURL = [account.provider.authEndpointURL URLByDeletingLastPathComponent];
 
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Authenticating..."
                                                                                                        andAddToView:self.view];
-        OpenStackRequest *request = [OpenStackRequest authenticationRequest:account];
-        request.completionBlock = ^{
-            [activityIndicatorView removeFromSuperview];
-            if ([request isSuccess]) {
-                NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
-                if (storageURLString) {
-                    account.filesURL = [NSURL URLWithString:storageURLString];
-                } else {
-                    account.filesURL = [[account.hostURL URLByAppendingPathComponent:@"v1"] URLByAppendingPathComponent:account.username];
-                }
-                [account persist];
-                [rootViewController.tableView reloadData];
-                [self.navigationController dismissModalViewControllerAnimated:YES];
+        [[account.manager authenticate]
+         success:^(OpenStackRequest *request) {
+             if ([request isSuccess]) {
+                 NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
+                 if (storageURLString) {
+                     account.filesURL = [NSURL URLWithString:storageURLString];
+                 } else {
+                     account.filesURL = [account.provider.authEndpointURL URLByAppendingPathComponent:account.username];
+                 }
+                 [account persist];
+                 [[account.manager userCatalogForDisplaynames:nil UUIDs:[NSArray arrayWithObject:account.username]]
+                  success:^(OpenStackRequest *request) {
+                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                      [rootViewController.tableView reloadData];
+                      [self.navigationController dismissModalViewControllerAnimated:YES];
+                  }
+                  failure:^(OpenStackRequest *request) {
+                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                      [rootViewController.tableView reloadData];
+                      [self.navigationController dismissModalViewControllerAnimated:YES];
+                  }];
             } else {
+                [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                 self.navigationItem.rightBarButtonItem.enabled = YES;
-                [self alert:@"Authentication Failure" message:@"Please check your Username and Token."];
+                [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
             }
-        };
-        request.failedBlock = ^{
-            [activityIndicatorView removeFromSuperview];
-            self.navigationItem.rightBarButtonItem.enabled = YES;
-            if ([request responseStatusCode] == 401) {
-                [self alert:@"Authentication Failure" message:@"Please check your Username and Token."];
-            } else {
-                [self failOnBadConnection];
-            }
-        };
-        [request startAsynchronous];
+         }
+         failure:^(OpenStackRequest *request) {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             self.navigationItem.rightBarButtonItem.enabled = YES;
+             if ([request responseStatusCode] == 401) {
+                 [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
+             } else {
+                 [self failOnBadConnection];
+             }
+         }];
     }
 }
 
index fa0c544..dcad36f 100644 (file)
@@ -45,5 +45,6 @@
 
 @property (nonatomic, retain) OpenStackAccount *account;
 @property (nonatomic, retain) NSMutableDictionary *groups;
+@property (nonatomic, retain) NSMutableDictionary *metadata;
 
 @end
index 906597a..7bac764 100644 (file)
@@ -47,7 +47,7 @@
 
 @implementation AccountGroupsViewController
 
-@synthesize account, groups;
+@synthesize account, groups, metadata;
 
 #pragma mark - View lifecycle
 
 - (void)viewWillAppear:(BOOL)animated {
     [super viewWillAppear:animated];
     self.navigationItem.title = @"Groups";
-    groups = [[NSMutableDictionary alloc] init];
-    metadata = [[NSMutableDictionary alloc] init];
+}
 
-    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading..."
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading groups..."
                                                                                                    andAddToView:self.view];
     [[self.account.manager getStorageAccountInfo]
      success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
-         for (NSString *key in request.responseHeaders) {
-             if ([key hasPrefix:@"X-Account-Group-"]) {
-                 NSString *groupUsers = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:key]];
-                 NSString *groupName = [NSString decodeFromPercentEscape:[key substringFromIndex:16]];
-                 [groups setObject:groupUsers forKey:groupName];
+         self.groups = [NSMutableDictionary dictionary];
+         self.metadata = [NSMutableDictionary dictionary];
+     
+         for (NSString *headerName in request.responseHeaders) {
+             if ([headerName hasPrefix:@"X-Account-Group-"]) {
+                 [groups setObject:[[NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:headerName]] componentsSeparatedByString:@","]
+                            forKey:[NSString decodeFromPercentEscape:[headerName substringFromIndex:16]]];
+              } else if ([headerName hasPrefix:@"X-Account-Meta-"]) {
+                 [metadata setObject:[NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:headerName]]
+                              forKey:[NSString decodeFromPercentEscape:[headerName substringFromIndex:15]]];
              }
          }
          
-         for (NSString *header in request.responseHeaders) {
-             if ([header hasPrefix:@"X-Account-Meta-"])  {
-                 NSString *metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:15]];
-                 NSString *metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
-                 [metadata setObject:metadataValue forKey:metadataKey];
-             }
+         NSMutableArray *UUIDs = [NSMutableArray array];
+         for (NSString *groupName in groups) {
+             [UUIDs addObjectsFromArray:[groups objectForKey:groupName]];
+         }
+         if (UUIDs.count) {
+             [[self.account.manager userCatalogForDisplaynames:nil UUIDs:UUIDs]
+              success:^(OpenStackRequest *request) {
+                  [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                  [self.tableView reloadData];
+              }
+              failure:^(OpenStackRequest *request) {
+                  [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                  [self.tableView reloadData];
+                  [self alert:@"Failed to translate group UUIDs." request:request];
+              }];
+         } else {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self.tableView reloadData];
          }
-         
-         [self.tableView reloadData];
      }
      failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
-         [self alert:@"Error" message:@"Failed to get account information"];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         [self alert:@"Failed to get account information." request:request];
      }];
 }
 
+#pragma mark - Internal
+
+- (NSMutableArray *)groupUsersForGroupName:(NSString *)groupName {
+    NSArray *groupUUIDs = [groups objectForKey:groupName];
+    NSMutableArray *groupUsers = [NSMutableArray arrayWithCapacity:groupUUIDs.count];
+    for (NSString *UUID in groupUUIDs) {
+        [groupUsers addObject:[self.account displaynameForUUID:UUID safe:YES]];
+    }
+    return groupUsers;
+}
+
+- (NSMutableString *)groupUsersStringForGroupName:(NSString *)groupName {
+    NSMutableArray *groupUsers = [self groupUsersForGroupName:groupName];
+    NSMutableString *groupUsersString = [NSMutableString string];
+    for (NSUInteger index = 0; index < groupUsers.count; index++) {
+        if (index) {
+            [groupUsersString appendString:@","];
+        }
+        [groupUsersString appendString:[groupUsers objectAtIndex:index]];
+    }
+    return groupUsersString;
+}
+
 #pragma mark - Memory management
 
 - (void)dealloc {
 #pragma mark - UITableViewDataSource
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    return [groups count] + 1;
+    return (groups.count + 1);
 }
 
 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
     CGFloat result;
-    if ([groups count] > 0 && indexPath.row < [groups count]) {
-        NSString *groupName = [[groups allKeys] objectAtIndex:indexPath.row];
-        NSString *groupUsers = [groups objectForKey:groupName];
-        result = 22.0 + [[NSString stringWithFormat:@"%@", groupUsers] sizeWithFont:[UIFont systemFontOfSize:18.0]
+    if (indexPath.row < groups.count) {
+        NSString *groupName = [[[groups allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
+        result = 22.0 + [[self groupUsersStringForGroupName:groupName] sizeWithFont:[UIFont systemFontOfSize:18.0]
                                                                   constrainedToSize:(([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ?
                                                                                      CGSizeMake(596.0, 9000.0) :
                                                                                      CGSizeMake(280.0, 9000.0))
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     static NSString *CellIdentifier = @"Cell";
-    
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil) {
         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
     }
     
     cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
-    if (indexPath.row < [groups count]) {
-        NSString *groupName = [[groups allKeys] objectAtIndex:indexPath.row];
+    if (indexPath.row < groups.count) {
+        NSString *groupName = [[[groups allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
         cell.textLabel.text = groupName;
-        cell.detailTextLabel.text = [groups objectForKey:groupName];
+        cell.detailTextLabel.text = [self groupUsersStringForGroupName:groupName];
         cell.detailTextLabel.numberOfLines = 0;
         cell.detailTextLabel.lineBreakMode = UILineBreakModeCharacterWrap;
     } else {
     vc.account = self.account;
     vc.metadata = metadata;
     vc.groups = groups;
-    if (indexPath.row < [groups count]) {
-        NSString *groupName = [[groups allKeys] objectAtIndex:indexPath.row];
-        NSString *groupUsers = [groups objectForKey:groupName];
-        
+    if (indexPath.row < groups.count) {
+        NSString *groupName = [[[groups allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
         vc.removeGroupEnabled = YES;
         vc.groupName = groupName;
-        vc.groupUsers = groupUsers;
         vc.navigationItem.title = @"Edit Group";
     } else {
         vc.removeGroupEnabled = NO;
         vc.groupName = @"";
-        vc.groupUsers = @"";
         vc.navigationItem.title = @"Add Group";
     }
     
index cfc04a0..a9509c0 100755 (executable)
@@ -56,7 +56,7 @@
 - (void)viewWillAppear:(BOOL)animated {
     [super viewWillAppear:animated];
 
-    self.navigationItem.title = self.account.username;
+    self.navigationItem.title = [self.account displaynameForUUID:self.account.username safe:YES];
     account.shared = NO;
     account.sharingAccount = nil;
     
index 57009a3..2ec4418 100755 (executable)
@@ -6,8 +6,6 @@
 //  The OpenStack project is provided under the Apache 2.0 license.
 //
 
-#import <Foundation/Foundation.h>
-
 // this class performs API calls on accounts and broadcasts NSNotifications to any other
 // object that chooses to observe the notification
 
     ASINetworkQueue *queue;
 }
 
-@property (nonatomic, retain) ASINetworkQueue *queue;
-
 @property (nonatomic, assign) OpenStackAccount *account;
+@property (nonatomic, retain) ASINetworkQueue *queue;
 
-- (NSString *)notificationName:(NSString *)key identifier:(NSString *)identifier;
-- (void)notify:(NSString *)name request:(OpenStackRequest *)request;
-- (void)notify:(NSString *)name request:(OpenStackRequest *)request object:(id)object;
+- (APICallback *)userCatalogForDisplaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs;
 
-// object storage
+- (APICallback *)authenticate;
+- (APICallback *)getSharingAccounts;
 
 - (APICallback *)getStorageAccountInfo;
-- (APICallback *)getSharingAccounts;
 - (APICallback *)getContainers;
+- (APICallback *)writeAccountMetadata:(NSDictionary *)accountInfo;
+
+- (APICallback *)getContainerInfo:(Container *)container;
 - (APICallback *)createContainer:(Container *)container;
 - (APICallback *)deleteContainer:(Container *)container;
-
-- (void)getObjects:(Container *)container;
 - (void)getObjects:(Container *)container afterMarker:(NSString *)marker objectsBuffer:(NSMutableDictionary *)objectsBuffer;
-- (APICallback *)getContainerInfo:(Container *)container;
+- (void)getObjects:(Container *)container;
+- (APICallback *)writeContainerPolicy:(Container *)container;
+
 - (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object;
 - (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object version:(NSString *)version;
 - (APICallback *)getObjectVersionsList:(Container *)container object:(StorageObject *)object;
-- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate;
-
-- (APICallback *)getObject:(Container *)container
-                    object:(StorageObject *)object
-  downloadProgressDelegate:(id)downloadProgressDelegate
+- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate
+           requestUserInfo:(NSDictionary *)requestUserInfo version:(NSString *)version;
+- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate
            requestUserInfo:(NSDictionary *)requestUserInfo;
-
-- (APICallback *)getObject:(Container *)container
-                    object:(StorageObject *)object
-  downloadProgressDelegate:(id)downloadProgressDelegate 
-           requestUserInfo:(NSDictionary *)requestUserInfo
-                   version:(NSString *)version;
-
-
+- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate;
 - (APICallback *)writeObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate;
 - (APICallback *)writeObjectMetadata:(Container *)container object:(StorageObject *)object;
 - (APICallback *)deleteObject:(Container *)container object:(StorageObject *)object;
 
-// account actions
-
-- (APICallback *)writeAccountMetadata:(NSDictionary *)accountInfo;
-
-// container actions
-
-- (APICallback *)writeContainerPolicy:(Container *)container;
+- (NSString *)notificationName:(NSString *)key identifier:(NSString *)identifier;
+- (void)notify:(NSString *)name request:(OpenStackRequest *)request;
+- (void)notify:(NSString *)name request:(OpenStackRequest *)request object:(id)object;
 
 @end
index 66f68f6..8d8a380 100755 (executable)
 #import "Container.h"
 #import "Folder.h"
 #import "StorageObject.h"
-#import "GetContainersRequest.h"
 #import "GetObjectsRequest.h"
 #import "ASINetworkQueue.h"
 #import "APICallback.h"
 #import "JSON.h"
 #import "OpenStackAppDelegate.h"
+#import "NSString+Conveniences.h"
 
 @implementation AccountManager
 
@@ -29,8 +29,8 @@
 - (APICallback *)callbackWithRequest:(id)request success:(APIResponseBlock)success failure:(APIResponseBlock)failure {
     APICallback *callback = [[[APICallback alloc] initWithAccount:self.account request:request] autorelease];
     ((OpenStackRequest *)request).delegate = self;
-    ((OpenStackRequest *)request).callback = callback; 
-
+    ((OpenStackRequest *)request).callback = callback;
+    
     [request setCompletionBlock:^{
         if ([request isSuccess]) {
             success(request);
@@ -44,7 +44,7 @@
         failure(request);
         [request notify];
     }];
-    [request startAsynchronous];    
+    [request startAsynchronous];
     return callback;
 }
 
     return [self callbackWithRequest:request success:^(OpenStackRequest *request){} failure:^(OpenStackRequest *request){}];
 }
 
-#pragma mark - Notification
-
-- (NSString *)notificationName:(NSString *)key identifier:(NSString *)identifier {
-    return [NSString stringWithFormat:@"%@-%@-%@", key, self.account.uuid, identifier];
-}
-
-- (void)requestFinished:(OpenStackRequest *)request {
-    NSString *notificationName = [request.userInfo objectForKey:@"notificationName"];
-    if (!notificationName)
-        return;
-    
-    id notificationObject = [request.userInfo objectForKey:@"notificationObject"];
-    if ([request isSuccess]) {
-        NSNotification *notification = [NSNotification notificationWithName:[NSString stringWithFormat:@"%@Succeeded", notificationName] object:notificationObject];
-        [[NSNotificationCenter defaultCenter] postNotification:notification];
-    } else {
-        NSNotification *notification = [NSNotification notificationWithName:[NSString stringWithFormat:@"%@Failed", notificationName] object:notificationObject userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
-        [[NSNotificationCenter defaultCenter] postNotification:notification];
-    }
+#pragma mark - API Calls
+#pragma mark User Catalog
+- (APICallback *)userCatalogForDisplaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs {
+    __block OpenStackRequest *request = [OpenStackRequest userCatalogRequest:self.account displaynames:displaynames UUIDs:UUIDs];
+    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
+        NSDictionary *catalogs = [request catalogs];
+        NSDictionary *displaynameCatalog = [catalogs objectForKey:@"displayname_catalog"];
+        for (NSString *displayname in displaynameCatalog) {
+            [self.account.userCatalog setObject:displayname forKey:[displaynameCatalog objectForKey:displayname]];
+        }
+        if (UUIDs) {
+            NSDictionary *UUIDCatalog = [catalogs objectForKey:@"uuid_catalog"];
+            for (NSString *UUID in UUIDs) {
+                NSString *displayname = [UUIDCatalog objectForKey:UUID];
+                if (displayname) {
+                    [self.account.userCatalog setObject:displayname forKey:UUID];
+                } else {
+                    [self.account.userCatalog removeObjectForKey:UUID];
+                }
+            }
+        }
+        [self.account persist];
+    }];
 }
 
-- (void)requestFailed:(OpenStackRequest *)request {
-    NSString *notificationName = [request.userInfo objectForKey:@"notificationName"];
-    if (!notificationName)
-        return;
-    
-    id notificationObject = [request.userInfo objectForKey:@"notificationObject"];
-    NSNotification *notification = [NSNotification notificationWithName:[NSString stringWithFormat:@"%@Failed", notificationName] object:notificationObject userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
-    [[NSNotificationCenter defaultCenter] postNotification:notification];
-}
+#pragma mark Top
 
-- (void)notify:(NSString *)name request:(OpenStackRequest *)request {
-    NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
-    [[NSNotificationCenter defaultCenter] postNotification:notification];
+- (APICallback *)authenticate {
+    __block OpenStackRequest *request = [OpenStackRequest authenticationRequest:self.account];
+    return [self callbackWithRequest:request];
 }
 
-- (void)notify:(NSString *)name request:(OpenStackRequest *)request object:(id)object {
-    NSNotification *notification = [NSNotification notificationWithName:name object:object userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
-    [[NSNotificationCenter defaultCenter] postNotification:notification];
+- (APICallback *)getSharingAccounts {
+    __block OpenStackRequest *request = [OpenStackRequest getSharingAccountsRequest:self.account];
+    return [self callbackWithRequest:request];
 }
 
-#pragma mark - API Calls
-
-#pragma mark Object Storage
+#pragma mark Account
 
 - (APICallback *)getStorageAccountInfo {
     __block OpenStackRequest *request = [OpenStackRequest getStorageAccountInfoRequest:self.account];
-    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {        
-        self.account.containerCount = [[[request responseHeaders] objectForKey:@"X-Account-Container-Count"] intValue];
+    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
         NSString *bytesUsedString = [request.responseHeaders objectForKey:@"X-Account-Bytes-Used"];
         self.account.bytesUsed = (bytesUsedString ?
                                   [NSNumber numberWithUnsignedLongLong:strtoull([bytesUsedString UTF8String], NULL, 0)] : nil);
         self.account.policyQuota = (policyQuotaString ?
                                     [NSNumber numberWithUnsignedLongLong:strtoull([policyQuotaString UTF8String], NULL, 0)] : nil);
         [self.account persist];
-        self.account.containerCount = [self.account.containers count];        
     }];
 }
 
-- (APICallback *)getSharingAccounts {
-    __block OpenStackRequest *request = [OpenStackRequest getSharingAccountsRequest:self.account];
-    return [self callbackWithRequest:request];        
+- (APICallback *)getContainers {
+    __block OpenStackRequest *request = [OpenStackRequest getContainersRequest:self.account];
+    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
+        self.account.containers = [request containers];
+        NSString *bytesUsedString = [request.responseHeaders objectForKey:@"X-Account-Bytes-Used"];
+        self.account.bytesUsed = (bytesUsedString ?
+                                  [NSNumber numberWithUnsignedLongLong:strtoull([bytesUsedString UTF8String], NULL, 0)] : nil);
+        NSString *policyQuotaString = [request.responseHeaders objectForKey:@"X-Account-Policy-Quota"];
+        self.account.policyQuota = (policyQuotaString ?
+                                    [NSNumber numberWithUnsignedLongLong:strtoull([policyQuotaString UTF8String], NULL, 0)] : nil);
+        [self.account persist];
+    }];
 }
 
-- (APICallback *)getContainers {
-    __block OpenStackRequest *request = [OpenStackRequest filesRequest:self.account method:@"GET" path:@""];
-    return [self callbackWithRequest:request];    
+- (APICallback *)writeAccountMetadata:(NSDictionary *)accountInfo {
+    __block OpenStackRequest *request = [OpenStackRequest writeAccountMetadataRequest:self.account withAccountInfo:accountInfo];
+    return [self callbackWithRequest:request];
+}
+
+#pragma mark Container
+
+- (APICallback *)getContainerInfo:(Container *)container {
+    __block OpenStackRequest *request = [OpenStackRequest getContainerInfoRequest:self.account container:container];
+    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
+        container.metadata = [NSMutableDictionary dictionary];
+        for (NSString *headerName in request.responseHeaders) {
+            if ([headerName hasPrefix:@"X-Container-Meta-"]) {
+                [container.metadata setObject:[NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:headerName]]
+                                                                         forKey:[NSString decodeFromPercentEscape:[headerName substringFromIndex:17]]];
+            }
+        }
+    }];
 }
 
 - (APICallback *)createContainer:(Container *)container {
     __block OpenStackRequest *request = [OpenStackRequest createContainerRequest:self.account container:container];
     return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
-        
-        [self.account.containers setObject:container forKey:container.name];        
+        [self.account.containers setObject:container forKey:container.name];
         [self.account persist];
-        self.account.containerCount = [self.account.containers count];        
     }];
 }
 
 - (APICallback *)deleteContainer:(Container *)container {
     __block OpenStackRequest *request = [OpenStackRequest deleteContainerRequest:self.account container:container];
-    return [self callbackWithRequest:request];
-    
-}
-
-- (void)getObjects:(Container *)container {
-    [self getObjects:container afterMarker:nil objectsBuffer:nil];
+    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
+    }];
 }
 
-- (void)getObjects:(Container *)container
-       afterMarker:(NSString *)marker
-     objectsBuffer:(NSMutableDictionary *)objectsBuffer {
-    if (![self queue]) {
-        [self setQueue:(ASINetworkQueue *)[[[NSOperationQueue alloc] init] autorelease]];
-    }    
+- (void)getObjects:(Container *)container afterMarker:(NSString *)marker objectsBuffer:(NSMutableDictionary *)objectsBuffer {
+    if (!self.queue) {
+        self.queue = [ASINetworkQueue queue];
+        self.queue.shouldCancelAllRequestsOnFailure = NO;
+        [self.queue go];
+    }
     GetObjectsRequest *request = [GetObjectsRequest request:self.account container:container marker:marker objectsBuffer:objectsBuffer];
     [queue addOperation:request];
 }
 
-- (void)getObjectsSucceeded:(OpenStackRequest *)request {
-    if ([request isSuccess]) {
-        Container *container = [request.userInfo objectForKey:@"container"];
-        NSMutableDictionary *objects = [request objects];
-        container.rootFolder = [Folder folder];
-        container.rootFolder.objects = objects;
-        [self.account persist];
-        
-        NSNotification *notification = [NSNotification notificationWithName:@"getObjectsSucceeded" object:self.account userInfo:[NSDictionary dictionaryWithObject:container forKey:@"container"]];
-        [[NSNotificationCenter defaultCenter] postNotification:notification];
-    } else {
-        NSNotification *notification = [NSNotification notificationWithName:@"getObjectsFailed" object:self.account userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
-        [[NSNotificationCenter defaultCenter] postNotification:notification];
-    }
-}
-
-- (void)getObjectsFailed:(OpenStackRequest *)request {
-    NSNotification *notification = [NSNotification notificationWithName:@"getObjectsFailed" object:self.account userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
-    [[NSNotificationCenter defaultCenter] postNotification:notification];
+- (void)getObjects:(Container *)container {
+    [self getObjects:container afterMarker:nil objectsBuffer:nil];
 }
 
-- (APICallback *)getContainerInfo:(Container *)container {
-    __block OpenStackRequest *request = [OpenStackRequest getContainerInfoRequest:self.account container:container];
+- (APICallback *)writeContainerPolicy:(Container *)container {
+    __block OpenStackRequest *request = [OpenStackRequest writeContainerPolicyRequest:self.account container:container];
     return [self callbackWithRequest:request];
 }
 
-- (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object {    
+#pragma mark Storage Object
+
+- (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object {
     __block OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:self.account container:container object:object];
     return [self callbackWithRequest:request];
 }
 
 - (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object version:(NSString *)version {
-    if (!version)
+    if (!version) {
         return [self getObjectInfo:container object:object];
+    }
     
     __block OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:self.account container:container object:object version:version];
     return [self callbackWithRequest:request];
     return [self callbackWithRequest:request];
 }
 
-- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate {
-    return [self getObject:container object:object downloadProgressDelegate:downloadProgressDelegate requestUserInfo:nil];
-}
-
-- (APICallback *)getObject:(Container *)container
-                    object:(StorageObject *)object
-  downloadProgressDelegate:(id)downloadProgressDelegate
-           requestUserInfo:(NSDictionary *)requestUserInfo {
-    
-    return [self getObject:container object:object downloadProgressDelegate:downloadProgressDelegate requestUserInfo:requestUserInfo version:nil];
-}
-
-- (APICallback *)getObject:(Container *)container
-                    object:(StorageObject *)object
-  downloadProgressDelegate:(id)downloadProgressDelegate 
-           requestUserInfo:(NSDictionary *)requestUserInfo
-                   version:(NSString *)version{
+- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate
+           requestUserInfo:(NSDictionary *)requestUserInfo version:(NSString *)version {
     __block OpenStackRequest *request = [OpenStackRequest getObjectRequest:self.account container:container object:object version:version];
     request.delegate = self;
     request.downloadProgressDelegate = downloadProgressDelegate;
-    request.showAccurateProgress = YES;  
+    request.showAccurateProgress = YES;
     if (requestUserInfo) {
         request.userInfo = requestUserInfo;
     }
     OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
     [app setObjectDownloadRequest:request forAccount:account container:container object:object];
     return [self callbackWithRequest:request
-    success:^(OpenStackRequest *request) {
-        if (!object.hash)
-            object.hash = [request.responseHeaders objectForKey:@"X-Object-Hash"];
-        OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
-        NSString *filePath = [app.cacheDirectoryPath stringByAppendingFormat:@"/%@.%@", object.hash, object.name.pathExtension];
-        [[request responseData] writeToFile:filePath atomically:YES];
-        [app setCacheFilePath:filePath forHash:object.hash];
-        [app removeObjectDownloadRequestForAccount:account container:container object:object];
-    }
-    failure:^(OpenStackRequest *request) {
-        OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
-        [app removeObjectDownloadRequestForAccount:account container:container object:object];
-    }];
+                             success:^(OpenStackRequest *request) {
+                                 if (!object.hash) {
+                                     object.hash = [request.responseHeaders objectForKey:@"X-Object-Hash"];
+                                 }
+                                 OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
+                                 NSString *filePath = [app.cacheDirectoryPath stringByAppendingFormat:@"/%@.%@", object.hash, object.name.pathExtension];
+                                 [[request responseData] writeToFile:filePath atomically:YES];
+                                 [app setCacheFilePath:filePath forHash:object.hash];
+                                 [app removeObjectDownloadRequestForAccount:account container:container object:object];
+                             }
+                             failure:^(OpenStackRequest *request) {
+                                 OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
+                                 [app removeObjectDownloadRequestForAccount:account container:container object:object];
+                             }];
+}
+
+- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate
+           requestUserInfo:(NSDictionary *)requestUserInfo {
+    return [self getObject:container object:object downloadProgressDelegate:downloadProgressDelegate requestUserInfo:requestUserInfo version:nil];
+}
+
+- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate {
+    return [self getObject:container object:object downloadProgressDelegate:downloadProgressDelegate requestUserInfo:nil version:nil];
 }
 
 - (APICallback *)writeObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate {
     request.delegate = self;
     request.uploadProgressDelegate = downloadProgressDelegate;
     request.showAccurateProgress = YES;
-    
+    return [self callbackWithRequest:request];
+}
+
+- (APICallback *)writeObjectMetadata:(Container *)container object:(StorageObject *)object {
+    __block OpenStackRequest *request = [OpenStackRequest writeObjectMetadataRequest:self.account container:container object:object];
     return [self callbackWithRequest:request];
 }
 
     return [self callbackWithRequest:request];
 }
 
-- (APICallback *)writeObjectMetadata:(Container *)container object:(StorageObject *)object {
-    __block OpenStackRequest *request = [OpenStackRequest writeObjectMetadataRequest:self.account container:container object:object];
-    return [self callbackWithRequest:request];
+#pragma mark - Notifications
+
+- (NSString *)notificationName:(NSString *)key identifier:(NSString *)identifier {
+    return [NSString stringWithFormat:@"%@-%@-%@", key, self.account.uuid, identifier];
 }
 
-- (APICallback *)writeAccountMetadata:(NSDictionary *)accountInfo {
-    __block OpenStackRequest *request = [OpenStackRequest writeAccountMetadataRequest:self.account withAccountInfo:accountInfo];
-    return [self callbackWithRequest:request];
+- (void)notify:(NSString *)name request:(OpenStackRequest *)request {
+    NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
 }
 
-- (APICallback *)writeContainerPolicy:(Container *)container {
-    __block OpenStackRequest *request = [OpenStackRequest writeContainerPolicyRequest:self.account container:container];
-    return [self callbackWithRequest:request];
+- (void)notify:(NSString *)name request:(OpenStackRequest *)request object:(id)object {
+    NSNotification *notification = [NSNotification notificationWithName:name object:object userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
 }
 
-#pragma mark - Memory Management
+#pragma mark - Request Delegates
 
-- (void)dealloc {
-    [super dealloc];
+- (void)requestFinished:(OpenStackRequest *)request {
+    NSString *notificationName = [request.userInfo objectForKey:@"notificationName"];
+    if (!notificationName)
+        return;
+    
+    id notificationObject = [request.userInfo objectForKey:@"notificationObject"];
+    if ([request isSuccess]) {
+        NSNotification *notification = [NSNotification notificationWithName:[NSString stringWithFormat:@"%@Succeeded", notificationName] object:notificationObject];
+        [[NSNotificationCenter defaultCenter] postNotification:notification];
+    } else {
+        NSNotification *notification = [NSNotification notificationWithName:[NSString stringWithFormat:@"%@Failed", notificationName] object:notificationObject userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
+        [[NSNotificationCenter defaultCenter] postNotification:notification];
+    }
+}
+
+- (void)requestFailed:(OpenStackRequest *)request {
+    NSString *notificationName = [request.userInfo objectForKey:@"notificationName"];
+    if (!notificationName)
+        return;
+    
+    id notificationObject = [request.userInfo objectForKey:@"notificationObject"];
+    NSNotification *notification = [NSNotification notificationWithName:[NSString stringWithFormat:@"%@Failed", notificationName] object:notificationObject userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
+}
+
+#pragma mark - Observers
+
+- (void)getObjectsSucceeded:(OpenStackRequest *)request {
+    if ([request isSuccess]) {
+        Container *container = [request.userInfo objectForKey:@"container"];
+        NSMutableDictionary *objects = [request objects];
+        container.rootFolder = [Folder folder];
+        container.rootFolder.objects = objects;
+        [self.account persist];
+        
+        NSNotification *notification = [NSNotification notificationWithName:@"getObjectsSucceeded" object:self.account userInfo:[NSDictionary dictionaryWithObject:container forKey:@"container"]];
+        [[NSNotificationCenter defaultCenter] postNotification:notification];
+    } else {
+        NSNotification *notification = [NSNotification notificationWithName:@"getObjectsFailed" object:self.account userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
+        [[NSNotificationCenter defaultCenter] postNotification:notification];
+    }
+}
+
+- (void)getObjectsFailed:(OpenStackRequest *)request {
+    NSNotification *notification = [NSNotification notificationWithName:@"getObjectsFailed" object:self.account userInfo:[NSDictionary dictionaryWithObject:request forKey:@"request"]];
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
 }
 
 @end
index 5b2e773..9c46a67 100755 (executable)
     
     NSString *username;
     NSString *authToken;
+    NSString *displayname;
 }
 
 @property (nonatomic, retain) OpenStackAccount *account;
 
 @property (nonatomic, retain) NSString *username;
 @property (nonatomic, retain) NSString *authToken;
+@property (nonatomic, retain) NSString *displayname;
 
 - (void)setUsername:(NSString *)aUsername andAuthToken:(NSString *)anAuthToken;
 - (void)saveButtonPressed:(id)sender;
index 848f626..f6121db 100755 (executable)
@@ -8,6 +8,8 @@
 
 #import "AccountSettingsViewController.h"
 #import "OpenStackAccount.h"
+#import "AccountManager.h"
+#import "APICallback.h"
 #import "ActivityIndicatorView.h"
 #import "Provider.h"
 #import "RSTextFieldCell.h"
 
 #define kUsername 0
 #define kAuthToken 1
+#define kDisplayname 2
 
 @implementation AccountSettingsViewController
 
-@synthesize account, username, authToken;
+@synthesize account, username, authToken, displayname;
 
 #pragma mark - View lifecycle
 
@@ -34,6 +37,7 @@
     self.navigationItem.title = @"Authentication";
     userDetailsSection = 0;
     getTokenSection = 1;
+    displayname = [[self.account displaynameForUUID:self.account.username safe:NO] retain];
     [self addSaveButton];
     self.navigationItem.rightBarButtonItem.enabled = NO;
 }
@@ -43,6 +47,7 @@
 - (void)dealloc {
     [username release];
     [authToken release];
+    [displayname release];
     [account release];
     [super dealloc];
 }
@@ -51,7 +56,7 @@
 
 - (void) updateSaveButtonForUsername:(NSString *)checkUsername andAuthToken:(NSString *)checkAuthToken {
     self.navigationItem.rightBarButtonItem.enabled = (checkUsername && checkUsername.length &&
-                                                      checkAuthToken &&checkAuthToken.length &&
+                                                      checkAuthToken && checkAuthToken.length &&
                                                       (![checkUsername isEqualToString:account.username] ||
                                                        ![checkAuthToken isEqualToString:account.authToken]));
 }
@@ -61,6 +66,7 @@
 - (void)setUsername:(NSString *)aUsername {
     [username release];
     username = [aUsername retain];
+    self.displayname = [self.account displaynameForUUID:username safe:NO];
     [self updateSaveButtonForUsername:username andAuthToken:authTokenTextField.text];
 }
 
     [self updateSaveButtonForUsername:usernameTextField.text andAuthToken:authToken];
 }
 
+- (void)setDisplayname:(NSString *)aDisplayname {
+    BOOL reloadDisplayname = (displayname && ![displayname isEqualToString:aDisplayname]) || (!displayname && aDisplayname);
+    [displayname release];
+    displayname = [aDisplayname retain];
+    if (reloadDisplayname) {
+        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:kDisplayname inSection:userDetailsSection]]
+                              withRowAnimation:UITableViewRowAnimationNone];
+    }
+}
+
 #pragma mark - Actions
 
 - (void)setUsername:(NSString *)aUsername andAuthToken:(NSString *)anAuthToken {
     if (aUsername) {
         [username release];
         username = [aUsername retain];
+        self.displayname = [self.account displaynameForUUID:username safe:NO];
     }
     if (anAuthToken) {
         [authToken release];
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
     if (section == userDetailsSection)
-        return 2;
+        return 3;
     else
         return 1;
 }
         [cell.textLabel setBackgroundColor:[UIColor clearColor]];
         
         if (indexPath.row == kUsername) {
-            cell.textLabel.text = @"Username";
+            cell.textLabel.text = @"UUID";
+            cell.userInteractionEnabled = YES;
+            cell.textField.delegate = self;
+            cell.textField.returnKeyType = UIReturnKeyNext;
+            cell.textField.text = (username ? username : self.account.username);
             usernameTextField = cell.textField;
-            usernameTextField.delegate = self;
-            usernameTextField.secureTextEntry = NO;
-            usernameTextField.returnKeyType = UIReturnKeyNext;
-            if (username) {
-                usernameTextField.text = username;
-            } else {
-                usernameTextField.text = self.account.username;
-            }
         } else if (indexPath.row == kAuthToken) {
             cell.textLabel.text = @"Token";
+            cell.userInteractionEnabled = YES;
+            cell.textField.delegate = self;
+            cell.textField.returnKeyType = UIReturnKeyDone;
+            cell.textField.text = (authToken ? authToken : self.account.authToken);
             authTokenTextField = cell.textField;
-            authTokenTextField.secureTextEntry = NO;
-            authTokenTextField.delegate = self;
-            authTokenTextField.returnKeyType = UIReturnKeyDone;
-            if (authToken) {
-                authTokenTextField.text = authToken;
-            } else {
-                authTokenTextField.text = self.account.authToken;
-            }
+        } else if (indexPath.row == kDisplayname) {
+            cell.textLabel.text = @"User";
+            cell.userInteractionEnabled = NO;
+            cell.textField.textColor = [UIColor grayColor];
+            cell.textField.delegate = nil;
+            cell.textField.text = displayname;
         }
     } else {
         cell = (RSTextFieldCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     if (indexPath.section == getTokenSection) {
         NSString *protocol = [[[[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleURLTypes"] objectAtIndex:0] objectForKey:@"CFBundleURLSchemes"] objectAtIndex:0];
-        NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", account.pithosLoginURLPrefix, protocol];
+        NSString *loginURL = [NSString stringWithFormat:@"%@?next=%@://login&force=", account.provider.loginURL, protocol];
         [[UIApplication sharedApplication] openURL:[NSURL URLWithString:loginURL]];
         [tableView deselectRowAtIndexPath:indexPath animated:NO];
     }
 
 - (void)authenticate {
     if (!usernameTextField.text || [usernameTextField.text isEqualToString:@""]) {
-        [self alert:nil message:@"Please enter your Username."];
+        [self alert:nil message:@"Please enter your UUID."];
         self.navigationItem.rightBarButtonItem.enabled = YES;
         [usernameTextField becomeFirstResponder];
     } else if (!authTokenTextField.text || [authTokenTextField.text isEqualToString:@""]) {
         
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Authenticating..."
                                                                                                        andAddToView:self.view];
-        OpenStackRequest *request = [OpenStackRequest authenticationRequest:temporaryAccount];
-        request.completionBlock = ^{
-            [activityIndicatorView removeFromSuperview];
-            if ([request isSuccess]) {
-                account.username = request.account.username;
-                account.authToken = request.account.authToken;
-                NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
-                if (storageURLString) {
-                    account.filesURL = [NSURL URLWithString:storageURLString];
-                } else {
-                    account.filesURL = [[account.hostURL URLByAppendingPathComponent:@"v1"] URLByAppendingPathComponent:account.username];
-                }
-                [account persist];
-            } else {
-                self.navigationItem.rightBarButtonItem.enabled = YES;
-                [self alert:@"Authentication Failure" message:@"Please check your Username and Token."];
-            }
-        };
-        request.failedBlock = ^{
-            [activityIndicatorView removeFromSuperview];
-            self.navigationItem.rightBarButtonItem.enabled = YES;
-            if ([request responseStatusCode] == 401) {
-                [self alert:@"Authentication Failure" message:@"Please check your Username and Token."];
-            } else {
-                [self failOnBadConnection];
-            }
-        };
-        [request startAsynchronous];
+        [[temporaryAccount.manager authenticate]
+         success:^(OpenStackRequest *request) {
+             if ([request isSuccess]) {
+                 account.username = request.account.username;
+                 account.authToken = request.account.authToken;
+                 NSString *storageURLString = [[request responseHeaders] objectForKey:@"X-Storage-Url"];
+                 if (storageURLString) {
+                     account.filesURL = [NSURL URLWithString:storageURLString];
+                 } else {
+                     account.filesURL = [account.provider.authEndpointURL URLByAppendingPathComponent:account.username];
+                 }
+                 [account persist];
+                 [[account.manager userCatalogForDisplaynames:nil
+                                                        UUIDs:[NSArray arrayWithObject:account.username]]
+                  success:^(OpenStackRequest *request) {
+                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                      self.displayname = [account displaynameForUUID:account.username safe:NO];
+                  }
+                  failure:^(OpenStackRequest *request) {
+                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                  }];
+             } else {
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                 self.navigationItem.rightBarButtonItem.enabled = YES;
+                 [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
+             }
+         }
+         failure:^(OpenStackRequest *request) {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             self.navigationItem.rightBarButtonItem.enabled = YES;
+             if ([request responseStatusCode] == 401) {
+                 [self alert:@"Authentication Failure" message:@"Please check your UUID and Token."];
+             } else {
+                 [self failOnBadConnection];
+             }
+         }];
     }
 }
 
 @end
-
index f165d4f..43f243d 100755 (executable)
@@ -29,6 +29,7 @@
 
 - (void)addToView:(UIView *)view scrollOffset:(CGFloat)offset;
 - (void)addToView:(UIView *)view;
-- (void)removeFromSuperview;
+
+- (void)stopAnimatingAndRemoveFromSuperview;
 
 @end
index 2a62ea6..bc145a2 100755 (executable)
     self.superview.userInteractionEnabled = NO;
 }
 
+- (void)willMoveToSuperview:(UIView *)newSuperview {
+    self.superview.userInteractionEnabled = YES;
+    [super willMoveToSuperview:newSuperview];
+}
+
 #pragma mark - Memory management
 
 - (void)dealloc {
     [self addToView:view scrollOffset:0.0];
 }
 
-- (void)removeFromSuperview {
+- (void)stopAnimatingAndRemoveFromSuperview {
     [self.spinner stopAnimating];
-    [UIView animateWithDuration:kFadeTime animations:^{
-        self.alpha = 0.0;
-    } completion:^(BOOL finished) {
-        self.superview.userInteractionEnabled = YES;
-        [super removeFromSuperview];
-    }];    
+    [UIView transitionWithView:self
+                      duration:kFadeTime
+                       options:UIViewAnimationOptionCurveEaseInOut|UIViewAnimationOptionTransitionNone
+                    animations:^{
+                        [self removeFromSuperview];
+                    }
+                    completion:nil];
 }
 
 @end
index a1b2089..9116082 100755 (executable)
@@ -9,12 +9,14 @@
 @class ContainersViewController, OpenStackAccount;
 
 @interface AddContainerViewController : UITableViewController <UITextFieldDelegate> {
-    UITextField *textField;
-    ContainersViewController *containersViewController;
     OpenStackAccount *account;
+    ContainersViewController *containersViewController;
+    
+    UITextField *containerNameTextField;
 }
 
-@property (nonatomic, retain) ContainersViewController *containersViewController;
 @property (nonatomic, retain) OpenStackAccount *account;
+@property (nonatomic, retain) ContainersViewController *containersViewController;
+@property (nonatomic, retain) UITextField *containerNameTextField;
 
 @end
index f099bdc..7782e67 100755 (executable)
@@ -18,7 +18,7 @@
 
 @implementation AddContainerViewController
 
-@synthesize containersViewController, account;
+@synthesize account, containersViewController, containerNameTextField;
 
 #pragma mark - View lifecycle
 
 
 - (void)viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
-    [textField becomeFirstResponder];
+    [containerNameTextField becomeFirstResponder];
 }
 
 #pragma mark - Memory management
 
 - (void)dealloc {
-    [containersViewController release];
     [account release];
+    [containersViewController release];
+    [containerNameTextField release];
     [super dealloc];
 }
 
+#pragma mark - Button handlers
+
+- (void)saveButtonPressed:(id)sender {
+    if ([containerNameTextField.text isEqualToString:@""]) {
+        [self alert:@"Invalid input" message:@"Container name cannot be empty."];
+    } else {
+        [containerNameTextField resignFirstResponder];
+        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Creating container..."
+                                                                                                       andAddToView:self.view];
+        Container *container = [account.containers objectForKey:containerNameTextField.text];
+        if (!container) {
+            container = [[[Container alloc] init] autorelease];
+            container.name = containerNameTextField.text;
+        }
+        [[self.account.manager createContainer:container]
+         success:^(OpenStackRequest *request) {
+             [containersViewController.tableView reloadData];
+             containersViewController.refreshWhenAppeared = YES;
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self dismissModalViewControllerAnimated:YES];
+         }
+         failure:^(OpenStackRequest *request) {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self alert:@"There was a problem creating your container." request:request];
+         }];
+    }
+}
+
 #pragma mark - UITableViewDataSource
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
     if (cell == nil) {
         cell = [[[RSTextFieldCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:cellIdentifier] autorelease];
         cell.modalPresentationStyle = UIModalPresentationFormSheet;
-        textField = cell.textField;
-        textField.delegate = self;
-        textField.clearButtonMode = UITextFieldViewModeWhileEditing;
+        cell.textLabel.text = @"Name";
+        cell.textField.delegate = self;
+        cell.textField.clearButtonMode = UITextFieldViewModeWhileEditing;
+        self.containerNameTextField = cell.textField;
     }
-    cell.textLabel.text = @"Name";
     return cell;
 }
 
     return NO;
 }
 
-#pragma mark - Button handlers
-
-- (void)saveButtonPressed:(id)sender {
-    if ([textField.text isEqualToString:@""]) {
-        [self alert:@"Container Name Required" message:@"Please enter a container name."];
-    } else {
-        [textField resignFirstResponder];
-        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Creating container..."
-                                                                                                       andAddToView:self.view];
-        Container *container = [[Container alloc] init];
-        container.name = textField.text;
-        [[self.account.manager createContainer:container] 
-         success:^(OpenStackRequest *request) {
-             [self.containersViewController.tableView reloadData];
-             [activityIndicatorView removeFromSuperview];
-             [self dismissModalViewControllerAnimated:YES];
-             [container release];
-         }
-         failure:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             [self alert:@"There was a problem creating your container." request:request];
-             [container release];
-         }];
-    }
-}
-
 @end
index cb143f4..92bc9c5 100755 (executable)
@@ -15,8 +15,6 @@
     Container *container;
     Folder *folder;
     FolderViewController *folderViewController;
-    id successObserver;
-    id failureObserver;
 
     BOOL hasPhotoLibrary;
     BOOL hasCamera;
index 7d441f3..1068b16 100755 (executable)
                  newFolder.name = [newFolder.name stringByAppendingString:@"/"];
              newFolder.parent = folder;
              newFolder.sharing = folder.sharing;
-             BOOL currentFolderIsRoot = NO;
-             if ([folderViewController.folder isEqual:container.rootFolder]) {
-                 currentFolderIsRoot = YES;
-             }
+             BOOL currentFolderIsRoot = (folderViewController.folder == container.rootFolder);
              [folder addFolder:newFolder];
              folderViewController.folder = folderViewController.folder;
              if (currentFolderIsRoot) {
                  container.count += 1;
                  container.rootFolder = folder;
-                 [self.account.containers setObject:container forKey:container.name];
              }
-             if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
-                 [folderViewController setDetailViewController];
-             
              allowOverwrite = NO;
-             [activityIndicatorView removeFromSuperview];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self dismissModalViewControllerAnimated:YES];
              [newFolder release];
              [object release];
          }
          failure:^(OpenStackRequest *request) {
              allowOverwrite = NO;
-             [activityIndicatorView removeFromSuperview];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self alert:@"There was a problem creating this folder." request:request];
              [object release];
          }];
index 672e3db..44972e8 100755 (executable)
@@ -13,8 +13,6 @@
     Container *container;
     Folder *folder;
     FolderViewController *folderViewController;
-    id successObserver;
-    id failureObserver;
 }
 
 @property (nonatomic, retain) OpenStackAccount *account;
index 9fe7c34..c95301e 100755 (executable)
@@ -14,8 +14,6 @@
     Container *container;
     Folder *folder;
     FolderViewController *folderViewController;
-    id successObserver;
-    id failureObserver;
 
     UIImage *image;
     NSData *data;
index 2bf7a06..1d25c5d 100755 (executable)
     }
     
     [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:1 inSection:kName]] withRowAnimation:UITableViewRowAnimationNone];    
-    [qualityActivityIndicatorView removeFromSuperview];
+    [qualityActivityIndicatorView stopAnimatingAndRemoveFromSuperview];
 }
 
 - (void)sliderFinished:(id)sender {
                                                                                                    andAddToView:self.view];
     [[self.account.manager writeObject:self.container object:object downloadProgressDelegate:activityIndicatorView.progressView]
      success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          object.data = nil;
          object.sharing = folder.sharing;
          object.lastModified = [ComputeModel dateFromRFC1123String:[request.responseHeaders objectForKey:@"Date"]];
-         BOOL currentFolderIsRoot = NO;
-         if ([folderViewController.folder isEqual:container.rootFolder]) {
-             currentFolderIsRoot = YES;
-         }
+         BOOL currentFolderIsRoot = (folderViewController.folder == container.rootFolder);
          [folder addObject:object];
          folderViewController.folder = folderViewController.folder;
          if (currentFolderIsRoot) {
              container.count += 1;
              container.rootFolder = folder;
-             [self.account.containers setObject:container forKey:container.name];
          }
-         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
-             [folderViewController setDetailViewController];
          allowOverwrite = NO;
-         [self dismissModalViewControllerAnimated:YES];
+         [[self.account.manager getObjectInfo:self.container object:object]
+          success:^(OpenStackRequest *request) {
+              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+              object.hash = [request.responseHeaders objectForKey:@"X-Object-Hash"];
+              [self dismissModalViewControllerAnimated:YES];
+          }
+          failure:^(OpenStackRequest *request) {
+              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+              [self dismissModalViewControllerAnimated:YES];
+          }];
      }
      failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          allowOverwrite = NO;
-         [self alert:@"There was a problem uploading this file." request:request];            
+         [self alert:@"There was a problem uploading the file." request:request];            
      }];
 }
 
index 3a53d85..f55e8ec 100755 (executable)
     Container *container;
     ContainersViewController *containersViewController;
     FolderViewController *rootFolderViewController;
-    id successObserver;
-    id failureObserver;
-    NSInteger deleteSection;
     NSInteger policySection;
-    NSIndexPath *selectedContainerIndexPath;
+    NSInteger deleteSection;
 }
 
 @property (nonatomic, retain) OpenStackAccount *account;
 @property (nonatomic, retain) Container *container;
 @property (nonatomic, retain) ContainersViewController *containersViewController;
 @property (nonatomic, retain) FolderViewController *rootFolderViewController;
-@property (nonatomic, retain) NSIndexPath *selectedContainerIndexPath;
 
 - (void)reloadMetadataSection;
 
index 165059e..47793c2 100755 (executable)
 
 @implementation ContainerDetailViewController
 
-@synthesize account, container, containersViewController, selectedContainerIndexPath, rootFolderViewController;
+@synthesize account, container, containersViewController, rootFolderViewController;
+
+#pragma mark - View lifecycle
 
 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
     return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
 }
 
-- (void)setBackgroundView {
-    if (!self.container) {
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    if (!account.sharingAccount) {
+        policySection = 2;
+        deleteSection = 3;
+    } else {
+        policySection = -1;
+        deleteSection = -1;
+    }
+}
+
+- (void)viewWillAppear:(BOOL)animated {
+    [super viewWillAppear:animated];
+    if (container) {
+        self.navigationItem.title = container.name;
+    } else {
         UIView *viewContainer = [[UIView alloc] init];
         viewContainer.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
         viewContainer.backgroundColor = [UIColor iPadTableBackgroundColor];
     }
 }
 
-#pragma mark -
-#pragma mark View lifecycle
-
-- (void)viewDidLoad {
-    [super viewDidLoad];
-    if (!account.sharingAccount) {
-        deleteSection = 3;
-        policySection = 2;
-    } else {
-        deleteSection = -1;
-        policySection = -1;
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    if (container && ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone)) {
+        [self reloadMetadataSection];
     }
 }
 
-- (void)viewWillAppear:(BOOL)animated {
-    [super viewWillAppear:animated];
-    if (container) {
-        self.navigationItem.title = container.name;
-    } 
-    [self setBackgroundView];
-    [self.tableView reloadData];
+#pragma mark - Memory management
+
+- (void)dealloc {
+    [account release];
+    [container release];
+    [containersViewController release];
+    [rootFolderViewController release];
+    [super dealloc];
 }
 
-- (void)viewDidAppear:(BOOL)animated {
-    [super viewDidAppear:animated];
-    if (self.container && !container.metadata && ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone))
-        [self reloadMetadataSection];
+#pragma mark - Internal
+
+- (void)delete {
+    containersViewController.deletedContainer = container;
+    [containersViewController.navigationController popViewControllerAnimated:YES];
+    [containersViewController setDetailViewControllerForContainer:nil];
 }
 
-- (void)viewWillDisappear:(BOOL)animated {
-    [super viewWillDisappear:animated];
+#pragma mark - Actions
+
+- (void)reloadMetadataSection {
+    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading metadata..."
+                                                                                                   andAddToView:self.view
+                                                                                                   scrollOffset:self.tableView.contentOffset.y];
+    [[self.account.manager getContainerInfo:container]
+     success:^(OpenStackRequest *request) {
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kMetadata] withRowAnimation:UITableViewRowAnimationFade];
+     }
+     failure:^(OpenStackRequest *request) {
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         [self alert:@"There was a problem getting the container's metadata." request:request];
+     }];
 }
 
-#pragma mark -
-#pragma mark Table view data source
+#pragma mark - UITableViewDataSource
 
-- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
-    if (section == deleteSection) {
-        return @"Only empty containers can be deleted.";
+- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
+    if (container) {
+        return (account.sharingAccount ? 2 : 4);
     } else {
-        return @"";
+        return 0;
     }
 }
 
-- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
-    if (self.container) {
-        if (account.sharingAccount)
-            return 2;
-        else
-            return 4;
+- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
+    if (section == deleteSection) {
+        return @"Only empty containers can be deleted.";
     } else {
-        return 0;
+        return nil;
     }
 }
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    // Return the number of rows in the section.
     if (section == kOverview) {
         return 2;
     } else if (section == kMetadata) {
-        if (account.sharingAccount)
-            return [container.metadata count];
-        else
-            return 1 + [container.metadata count];     
+        return (account.sharingAccount ? container.metadata.count : (container.metadata.count + 1));
     } else if (section == policySection) {
-        if (account.sharingAccount)
-            return 2;
-        else
-            return 3;
+        return (account.sharingAccount ? 2 : 3);
     } else {
         return 1;
     }
 }
 
-// Customize the appearance of table view cells.
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     if (indexPath.section == deleteSection) {
         static NSString *CellIdentifier = @"DeleteCell";
             cell.textLabel.textAlignment = UITextAlignmentCenter;
             cell.textLabel.text = @"Delete Container";
         }
-        if ((self.container.count == 0) ||
-            (self.container.rootFolder && !self.container.rootFolder.objectsAndFoldersCount)) {
+        if (!self.container.count || (self.container.rootFolder && !self.container.rootFolder.objectsAndFoldersCount)) {
             cell.textLabel.textColor = [UIColor blackColor];
             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
+            cell.userInteractionEnabled = YES;
         } else {
             cell.textLabel.textColor = [UIColor grayColor];
             cell.selectionStyle = UITableViewCellSelectionStyleNone;
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil) {
         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
-        cell.selectionStyle = UITableViewCellSelectionStyleNone;
         cell.textLabel.backgroundColor = [UIColor clearColor];
         cell.detailTextLabel.backgroundColor = [UIColor clearColor];
         cell.detailTextLabel.numberOfLines = 0;
-        cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
+        cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
         cell.detailTextLabel.textAlignment = UITextAlignmentLeft;
     }
     cell.accessoryType = UITableViewCellAccessoryNone;
     cell.selectionStyle = UITableViewCellSelectionStyleNone;
+    cell.userInteractionEnabled = NO;
     
     if (indexPath.section == kOverview) {
-        cell.accessoryView = nil;
         if (indexPath.row == 0) {
             cell.textLabel.text = @"Name";
             cell.detailTextLabel.text = container.name;
         } else if (indexPath.row == 1) {
             cell.textLabel.text = @"Size";
-            cell.detailTextLabel.text = [container humanizedSize];
+            cell.detailTextLabel.text = [container osxStyleHumanizedSize];
         }
     } else if (indexPath.section == kMetadata) {
-        if (account.sharingAccount) {
-            cell.accessoryType = UITableViewCellAccessoryNone;
-            cell.selectionStyle = UITableViewCellSelectionStyleNone;
-            cell.userInteractionEnabled = NO;
-        }
-        else {
-            cell.selectionStyle = UITableViewCellSelectionStyleBlue;
+        if (!account.sharingAccount) {
             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+            cell.selectionStyle = UITableViewCellSelectionStyleBlue;
+            cell.userInteractionEnabled = YES;
         }
-        if (indexPath.row == [container.metadata count]) {
+        if (indexPath.row == container.metadata.count) {
             cell.textLabel.text = @"Add Metadata";
-            cell.detailTextLabel.text = @"";
+            cell.detailTextLabel.text = nil;
         } else {
-            NSString *key = [[container.metadata allKeys] objectAtIndex:indexPath.row];
-            NSString *value = [container.metadata objectForKey:key];
-            NSString *metadataKeyCellText = key;
-            NSString *metadataValueCellText = value;
-            if ([metadataKeyCellText length] > maxMetadataViewableLength) {
-                metadataKeyCellText = [metadataKeyCellText substringToIndex:(maxMetadataViewableLength - 3)];
-                metadataKeyCellText = [metadataKeyCellText stringByAppendingString:@"..."];
-            }
-            if ([metadataValueCellText length] > maxMetadataViewableLength) {
-                metadataValueCellText = [metadataValueCellText substringToIndex:(maxMetadataViewableLength - 3)];
-                metadataValueCellText = [metadataValueCellText stringByAppendingString:@"..."];
+            NSString *key = [[[container.metadata allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
+            if (key.length > maxMetadataViewableLength) {
+                cell.textLabel.text = [[key substringToIndex:(maxMetadataViewableLength - 3)] stringByAppendingString:@"..."];
+            } else {
+                cell.textLabel.text = key;
             }
-            cell.textLabel.text = metadataKeyCellText;
-            cell.detailTextLabel.text = metadataValueCellText;
+            cell.detailTextLabel.text = [container.metadata objectForKey:key];
         }
     } else if (indexPath.section == policySection) {
-        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
         if (indexPath.row == 0) {
             cell.textLabel.text = @"Versioning";
             cell.detailTextLabel.text = container.versioning;
-            cell.accessoryType = UITableViewCellAccessoryNone;
-            cell.selectionStyle = UITableViewCellSelectionStyleNone;
-        }
-        else if (indexPath.row == 1) {
+        } else if (indexPath.row == 1) {
             cell.textLabel.text = @"Quota";
             cell.detailTextLabel.text = [NSString stringWithFormat:@"%u", container.quota];
-            cell.accessoryType = UITableViewCellAccessoryNone;
-            cell.selectionStyle = UITableViewCellSelectionStyleNone;
-        }
-        else {
+        } else if (indexPath.row == 2) {
             cell.textLabel.text = @"Edit policy";
-            cell.detailTextLabel.text = @"";
+            cell.detailTextLabel.text = nil;
             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
+            cell.selectionStyle = UITableViewCellSelectionStyleBlue;
+            cell.userInteractionEnabled = YES;            
         }
     }
     
     return cell;
 }
 
-#pragma mark -
-#pragma mark Table view delegate
+#pragma mark - UITableViewDelegate
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    
     if (indexPath.section == kMetadata) {
         EditMetadataViewController *vc = [[EditMetadataViewController alloc] initWithNibName:@"EditMetadataViewController" bundle:nil];
-        NSString *metadataKey;
-        NSString *metadataValue;
-        
-        if (indexPath.row == [self.container.metadata count]) {
-            metadataKey = @"";
-            metadataValue = @"";
+        if (indexPath.row == container.metadata.count) {
+            vc.metadataKey = @"";
+            vc.metadataValue = @"";
             vc.removeMetadataEnabled = FALSE;
             vc.navigationItem.title = @"Add Metadata";
-        }
-        else {
-            metadataKey = [[self.container.metadata allKeys] objectAtIndex:indexPath.row];
-            metadataValue = [self.container.metadata objectForKey:metadataKey];
+        } else {
+            vc.metadataKey = [[[container.metadata allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
+            vc.metadataValue = [container.metadata objectForKey:vc.metadataKey];
             vc.removeMetadataEnabled = TRUE;
             vc.navigationItem.title = @"Edit Metadata";
         }
         object.metadata = container.metadata;
         object.fullPath = @"";
         
-        vc.metadataKey = metadataKey;
-        vc.metadataValue = metadataValue;
         vc.account = account;
         vc.container = container;
         vc.object = object;
         
         [self.navigationController pushViewController:vc animated:YES];
         [vc release];
-    } else if (indexPath.section == policySection) {
-        if (indexPath.row == 2) {
-            EditPolicyViewController *vc = [[EditPolicyViewController alloc] initWithNibName:@"EditPolicyViewController" bundle:nil];
-    
-            vc.account = account;
-            vc.container = container;
-            vc.containerDetailViewController = self;
-        
-            [self.navigationController pushViewController:vc animated:YES];
-            [vc release];
-        }
-    } else if (indexPath.section == deleteSection) {
-        if (self.container.count == 0 || !self.container.rootFolder.objectsAndFoldersCount) {
-            UIActionSheet *deleteActionSheet = [[[UIActionSheet alloc] initWithTitle:@"Are you sure you want to delete this container? This operation cannot be undone."
-                                                                            delegate:self
-                                                                   cancelButtonTitle:@"Cancel"
-                                                              destructiveButtonTitle:@"Delete Container"
-                                                                   otherButtonTitles:nil] autorelease];
-            [deleteActionSheet showInView:self.view];
-        }
+    } else if ((indexPath.section == policySection) && (indexPath.row == 2)) {
+        EditPolicyViewController *vc = [[EditPolicyViewController alloc] initWithNibName:@"EditPolicyViewController" bundle:nil];
+        vc.account = account;
+        vc.container = container;
+        vc.containerDetailViewController = self;
+        [self.navigationController pushViewController:vc animated:YES];
+        [vc release];
+    } else if ((indexPath.section == deleteSection) && (!self.container.count || (!self.container.rootFolder && !self.container.rootFolder.objectsAndFoldersCount))) {
+        UIActionSheet *deleteActionSheet = [[[UIActionSheet alloc] initWithTitle:@"Are you sure you want to delete this container? This operation cannot be undone."
+                                                                        delegate:self
+                                                               cancelButtonTitle:@"Cancel"
+                                                          destructiveButtonTitle:@"Delete Container"
+                                                               otherButtonTitles:nil] autorelease];
+        [deleteActionSheet showInView:self.view];
     }
 }
 
-#pragma mark -
-#pragma mark Action Sheet
-
-- (void)deleteContainerRow {
-    if ([self.account.containers count] == 0) {
-        [self.containersViewController.tableView reloadData];
-    } else {
-        [self.containersViewController.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedContainerIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
-    }
-    [self.rootFolderViewController.navigationController popViewControllerAnimated:YES];
-}
+#pragma mark - UIActionSheetDelegate
 
 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
     if (buttonIndex == 0) {
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Deleting container..."
                                                                                                        andAddToView:self.view
                                                                                                        scrollOffset:self.tableView.contentOffset.y];
-        [[self.account.manager deleteContainer:self.container]
+        [[self.account.manager deleteContainer:container]
          success:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             [self.account.containers removeObjectForKey:self.container.name];
-             [self.account persist];
-             if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-                [self.containersViewController.navigationController popViewControllerAnimated:YES];
-             } else {
-                [self.navigationController popViewControllerAnimated:YES];
-             }
-             [self deleteContainerRow];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self delete];
          }
          failure:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
             // 404 Not Found means it's not there, so we can show the user that it's deleted
-            if ([request responseStatusCode] == 404) {
-                [self.account.containers removeObjectForKey:container.name];
-                [self.account persist];
+            if (request.responseStatusCode == 404) {
+                [self delete];
             } else {
                 [self alert:@"There was a problem deleting this container." request:request];
             }
     [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:deleteSection] animated:YES];
 }
 
-
-#pragma mark -
-#pragma mark Memory management
-
-- (void)dealloc {
-    [account release];
-    [container release];
-    [containersViewController release];
-    [selectedContainerIndexPath release];
-    [rootFolderViewController release];
-    [super dealloc];
-}
-
-#pragma mark -
-#pragma mark Helper functions
-
-- (void)reloadMetadataSection {
-    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading metadata..."
-                                                                                                   andAddToView:self.view
-                                                                                                   scrollOffset:self.tableView.contentOffset.y];
-    [[self.account.manager getContainerInfo:container]
-     success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
-         container.metadata = [NSMutableDictionary dictionary];
-         for (NSString *header in request.responseHeaders) {
-             NSString *metadataKey;
-             NSString *metadataValue;
-             if ([header rangeOfString:@"X-Container-Meta-"].location != NSNotFound) {
-                 metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:17]];
-                 metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
-                 [container.metadata setObject:metadataValue forKey:metadataKey];
-             }
-         } 
-         NSIndexSet *metadataSections = [NSIndexSet indexSetWithIndex:kMetadata];
-         [self.tableView reloadSections:metadataSections withRowAnimation:UITableViewRowAnimationFade];
-     }
-     failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
-         [self alert:@"There was a problem retrieving the container's metadata." request:request]; 
-     }];
-}
-
 @end
-
index d4b5f51..7ba2b8a 100755 (executable)
@@ -8,24 +8,30 @@
 
 #import "OpenStackViewController.h"
 
-@class OpenStackAccount, AccountHomeViewController, ContainerDetailViewController;
+@class OpenStackAccount, AccountHomeViewController, ContainerDetailViewController, Container;
 
 @interface ContainersViewController : OpenStackViewController <UITableViewDelegate, UITableViewDataSource> {
-    IBOutlet UITableView *tableView;
     OpenStackAccount *account;
-        
-    BOOL containersLoaded;
-    NSString *accountUsageInfo;
     AccountHomeViewController *accountHomeViewController;
+    BOOL refreshWhenAppeared;
+    Container *deletedContainer;
+
+    UITableView *tableView;
+    
     ContainerDetailViewController *containerDetailViewController;
 }
 
-@property (nonatomic, retain) IBOutlet UITableView *tableView;
 @property (nonatomic, retain) OpenStackAccount *account;
-@property (nonatomic, readonly) NSString *accountUsageInfo;
 @property (nonatomic, assign) AccountHomeViewController *accountHomeViewController;
+@property (nonatomic, assign) BOOL refreshWhenAppeared;
+@property (nonatomic, retain) Container *deletedContainer;
+
+@property (nonatomic, retain) IBOutlet UITableView *tableView;
+
 @property (nonatomic, retain) ContainerDetailViewController *containerDetailViewController;
 
 - (IBAction)refreshButtonPressed:(id)sender;
+- (IBAction)homeButtonPressed:(id)sender;
+- (void)setDetailViewControllerForContainer:(Container *)container;
 
 @end
index 42d64bf..8045bda 100755 (executable)
 
 @implementation ContainersViewController
 
-@synthesize tableView, account, accountUsageInfo, accountHomeViewController, containerDetailViewController;
+@synthesize account, accountHomeViewController, refreshWhenAppeared, deletedContainer;
+@synthesize tableView;
+@synthesize containerDetailViewController;
 
 #pragma mark - View lifecycle
 
 - (void)viewDidLoad {
     [super viewDidLoad];
     self.navigationItem.title = @"Containers";
-    [self addAddButton]; 
-    [self addHomeButton];
+    [self addAddButton];
+    self.navigationItem.rightBarButtonItem.enabled = (!account.shared && !account.sharingAccount);
 }
 
 - (void)viewWillAppear:(BOOL)animated {
     [super viewWillAppear:animated];
-        
-    if ([self.account.containers count] == 0) {
-        self.tableView.allowsSelection = NO;
-        self.tableView.scrollEnabled = NO;
-        [self.tableView reloadData];        
-    }   
-    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-        if ([self.account.containers count] == 0 ) {
-            NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
-            [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
-            [self tableView:self.tableView didSelectRowAtIndexPath:indexPath];
-        } else if ([self.tableView indexPathForSelectedRow].row == 0 && [self.tableView indexPathForSelectedRow].section == 0) {
-            ContainerDetailViewController *vc = [[ContainerDetailViewController alloc] initWithNibName:@"ContainerDetailViewController" bundle:nil];
-            [self presentPrimaryViewController:vc];
-            [vc release]; 
+    if (!account.containers || !deletedContainer || ![account.containers objectForKey:deletedContainer.name]) {
+        // No containers loaded yet, or no deleted container to remove cell.
+        refreshWhenAppeared = (refreshWhenAppeared || !account.containers);
+        self.deletedContainer = nil;
+    }
+    if (account.shared) {
+        refreshWhenAppeared = YES;
+    }
+    if (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) && ![self.tableView indexPathForSelectedRow]) {
+        [self setDetailViewControllerForContainer:nil];
+    }
+    [self showToolbarInfoMessage:[self accountUsageInfo]];
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+    [super viewDidAppear:animated];
+    if (deletedContainer) {
+        self.deletedContainer = [account.containers objectForKey:deletedContainer.name];
+        if (deletedContainer) {
+            NSUInteger deletedContainerIndex = [account.pithosSortedContainers indexOfObject:deletedContainer];
+            if (deletedContainerIndex != NSNotFound) {
+                [account.containers removeObjectForKey:deletedContainer.name];
+                [account persist];
+                [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:deletedContainerIndex inSection:0]] withRowAnimation:UITableViewRowAnimationLeft];
+            }
         }
+        refreshWhenAppeared = YES;
+        self.deletedContainer = nil;
+    }
+    if (refreshWhenAppeared) {
+        refreshWhenAppeared = NO;
+        [self refreshButtonPressed:nil];
     }
-    
-    [self refreshButtonPressed:nil];
 }
 
 #pragma mark - Memory management
 
 - (void)dealloc {
-    [tableView release];
     [account release];
+    [tableView release];
     [containerDetailViewController release];
+    [deletedContainer release];
     [super dealloc];
 }
 
 #pragma mark - Internal
 
-- (void)createContainerWithName:(NSString *)containerName {
-    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Creating container..."
-                                                                                                   andAddToView:self.view];
-
-    Container *container = [[Container alloc] init];
-    container.name = containerName;
+- (void)createContainersWithNames:(NSArray *)containersNames activityIndicatorView:(ActivityIndicatorView *)previousActivityIndicatorView {
+    if (!containersNames.count)
+        return;
+    __block ActivityIndicatorView *activityIndicatorView = (previousActivityIndicatorView ?
+                                                            previousActivityIndicatorView :
+                                                            [ActivityIndicatorView activityIndicatorViewWithText:((containersNames.count == 1) ? @"Creating container..." : @"Creating containers...")
+                                                                                                    andAddToView:self.view]);
+    __block Container *container = [[Container alloc] init];
+    container.name = [containersNames objectAtIndex:0];
     [[self.account.manager createContainer:container]
      success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
-         [self.tableView reloadData];
+         [container release];
+         if (containersNames.count > 1) {
+             [self createContainersWithNames:[containersNames subarrayWithRange:NSMakeRange(1, containersNames.count - 1)]
+                       activityIndicatorView:activityIndicatorView];
+         } else {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self.tableView reloadData];
+         }
      }
      failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [container release];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [self alert:@"There was a problem creating your container." request:request];
      }];
-    [container release];
 }
 
-#pragma mark - Properties
-
 - (NSString *)accountUsageInfo {
     if (self.account.sharingAccount)
         return nil;
     }
 }
 
+- (ContainerDetailViewController *)containerDetailViewControllerForContainer:(Container *)container {
+    if (containerDetailViewController && (containerDetailViewController.container == container)) {
+        return containerDetailViewController;
+    }
+    
+    ContainerDetailViewController *cdvc = [[[ContainerDetailViewController alloc] initWithNibName:@"ContainerDetailViewController" bundle:nil] autorelease];
+    if (container) {
+        cdvc.account = account;
+        cdvc.container = container;
+        cdvc.containersViewController = self;
+        if (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) && (self.navigationController.topViewController != self)) {
+            cdvc.rootFolderViewController = [self.navigationController.viewControllers
+                                           objectAtIndex:([self.navigationController.viewControllers indexOfObject:self] + 1)];
+        }
+    }
+    return cdvc;
+}
+
+#pragma mark - Actions
+
+- (void)setDetailViewControllerForContainer:(Container *)container {
+    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
+        ContainerDetailViewController *cdvc = [self containerDetailViewControllerForContainer:container];
+        [self presentPrimaryViewController:cdvc];
+        self.containerDetailViewController = cdvc;
+    }
+}
+
 #pragma mark - Button Handlers
 
 - (void)addButtonPressed:(id)sender {
     [vc release];
 }
 
-- (IBAction)homeButtonPressed:(id)sender {
-    [self.navigationController popToViewController:accountHomeViewController animated:YES];
-}
-
 - (void)refreshButtonPressed:(id)sender {
     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading..."
                                                                                                    andAddToView:self.view];
     [[self.account.manager getContainers] success:^(OpenStackRequest *request) {
-        self.account.containers = [request containers];
-        self.account.containerCount = [self.account.containers count];
-        NSString *bytesUsedString = [request.responseHeaders objectForKey:@"X-Account-Bytes-Used"];
-        self.account.bytesUsed = (bytesUsedString ?
-                                  [NSNumber numberWithUnsignedLongLong:strtoull([bytesUsedString UTF8String], NULL, 0)] : nil);
-        NSString *policyQuotaString = [request.responseHeaders objectForKey:@"X-Account-Policy-Quota"];
-        self.account.policyQuota = (policyQuotaString ?
-                                    [NSNumber numberWithUnsignedLongLong:strtoull([policyQuotaString UTF8String], NULL, 0)] : nil);
-        
-        [self.account persist];
-        containersLoaded = YES;
-        
-        [activityIndicatorView removeFromSuperview];
-        [self showToolbarInfoMessage:self.accountUsageInfo];
+        [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+        [self showToolbarInfoMessage:[self accountUsageInfo]];
         [self.tableView reloadData];
         if (!account.shared && !account.sharingAccount) {
-            if (![self.account.containers objectForKey:@"pithos"])
-                [self createContainerWithName:@"pithos"];
-            if (![self.account.containers objectForKey:@"trash"])
-                [self createContainerWithName:@"trash"];
+            NSMutableArray *containersNames = [NSMutableArray array];
+            if (![self.account.containers objectForKey:@"pithos"]) {
+                [containersNames addObject:@"pithos"];
+            }
+            if (![self.account.containers objectForKey:@"trash"]) {
+                [containersNames addObject:@"trash"];
+            }
+            [self createContainersWithNames:containersNames activityIndicatorView:nil];
         }
     } failure:^(OpenStackRequest *request) {
-        containersLoaded = NO;
-        [activityIndicatorView removeFromSuperview];
-        [self showToolbarInfoMessage:self.accountUsageInfo];
-        if (request.responseStatusCode != 0) {
-            [self alert:@"There was a problem loading containers." request:request addAccountSettingsButton:YES];
-        }
+        [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+        [self alert:@"There was a problem loading containers." request:request addAccountSettingsButton:YES];
     }];
 }
 
+- (void)homeButtonPressed:(id)sender {
+    [self.navigationController popToViewController:accountHomeViewController animated:YES];
+}
+
 #pragma mark - UITableViewDataSource
 
-- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if ([self.account.containers count] == 0) {
-        self.tableView.allowsSelection = NO;
-        self.tableView.scrollEnabled = NO;
+- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
+    if (!self.account.containers.count) {
+        aTableView.allowsSelection = NO;
+        aTableView.scrollEnabled = NO;
     } else {
-        self.tableView.allowsSelection = YES;
-        self.tableView.scrollEnabled = YES;
+        aTableView.allowsSelection = YES;
+        aTableView.scrollEnabled = YES;
     }
-    if (!containersLoaded && [self.account.containers count] == 0) {
+    if (!self.account.containers) {
         return 0;
     } else {
-        return MAX(1, [account.containers count]);    
+        return MAX(1, self.account.containers.count);
     }
+
 }
 
 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
-    if ([account.containers count] == 0) {
+    if (!self.account.containers.count) {
         return aTableView.frame.size.height;
     } else {
         return aTableView.rowHeight;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    if (containersLoaded && [self.account.containers count] == 0) {
-        NSString *noContainersTitle = self.account.shared ? @"No shared containers" : @"No containers";
-        NSString *noContainersSubtitle = self.account.shared ? @"" : @"Tap the + button to create a new container";
-        self.navigationItem.rightBarButtonItem.enabled = !self.account.shared;
-        
-        return [self tableView:tableView
+    if (self.account.containers && !self.account.containers.count) {
+        return [self tableView:aTableView
             emptyCellWithImage:[UIImage imageNamed:@"empty-containers.png"]
-                         title:noContainersTitle
-                      subtitle:noContainersSubtitle];
-    } else {   
+                         title:(self.account.shared ? @"No shared containers" : @"No containers")
+                      subtitle:(self.account.shared ? @"" : @"Tap the + button to create a new container")];
+    } else {
         static NSString *CellIdentifier = @"Cell";
-        
         UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
         if (cell == nil) {
             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
                 cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
             }
         }
+        
         Container *container = [self.account.pithosSortedContainers objectAtIndex:indexPath.row];
         cell.textLabel.text = container.name;
-        if ([container.name isEqualToString:@"pithos"])
+        if ([container.name isEqualToString:@"pithos"] ) {
             cell.imageView.image = [UIImage imageNamed:@"PithosContainerIcon.png"];
-        else if ([container.name isEqualToString:@"trash"])
+        } else if ([container.name isEqualToString:@"trash"]) {
             cell.imageView.image = [UIImage imageNamed:@"TrashIcon.png"];
-        else
+        } else {
             cell.imageView.image = [UIImage imageNamed:@"ContainerIcon.png"];
-                                    
-        if (!self.account.sharingAccount)
+        }
+        if (!self.account.sharingAccount) {
             cell.detailTextLabel.text = [container osxStyleHumanizedSize];
+        }
         
         return cell;
     }
 #pragma mark - UITableViewDelegate
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    Container *container = nil;
-    if ([account.containers count] > 0) {
-        container = [self.account.pithosSortedContainers objectAtIndex:indexPath.row];
-
-        FolderViewController *vc = (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ?
+    if (account.containers.count) {
+        Container *container = [self.account.pithosSortedContainers objectAtIndex:indexPath.row];
+        FolderViewController *fvc = (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ?
                                     [[FolderViewController alloc] initWithNibName:@"FolderViewController-iPad" bundle:nil] :
                                     [[FolderViewController alloc] initWithNibName:@"FolderViewController" bundle:nil]);
-        vc.account = self.account;
-        vc.container = container;
-        vc.containersViewController = self;
-        vc.selectedContainerIndexPath = indexPath;
-        [self.navigationController pushViewController:vc animated:YES];
-        vc.folder = container.rootFolder;
-        [vc release];
-        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-    }
-    
-    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-        ContainerDetailViewController *vc = [[ContainerDetailViewController alloc] initWithNibName:@"ContainerDetailViewController" bundle:nil];
-        vc.account = self.account;
-        vc.container = container;
-        vc.containersViewController = self;
-        vc.selectedContainerIndexPath = indexPath;
-        [self presentPrimaryViewController:vc];
-        self.containerDetailViewController = vc;
-        [vc release];
+        fvc.account = self.account;
+        fvc.container = container;
+        fvc.containersViewController = self;
+        [self.navigationController pushViewController:fvc animated:YES];
+        fvc.folder = container.rootFolder;
+        [fvc release];
+        [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
+
+        [self setDetailViewControllerForContainer:container];
     }
 }
 
 - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
-    Container *container = nil;
-    if ([account.containers count] > 0) {
-        container = [self.account.pithosSortedContainers objectAtIndex:indexPath.row];
+    if (account.containers.count) {
+        Container *container = [self.account.pithosSortedContainers objectAtIndex:indexPath.row];
+        ContainerDetailViewController *cdvc = [self containerDetailViewControllerForContainer:container];
+        [self.navigationController pushViewController:cdvc animated:YES];
     }
-    ContainerDetailViewController *vc = [[ContainerDetailViewController alloc] initWithNibName:@"ContainerDetailViewController" bundle:nil];
-    vc.account = self.account;
-    vc.container = container;
-    vc.containersViewController = self;
-    vc.selectedContainerIndexPath = indexPath;
-    [self.navigationController pushViewController:vc animated:YES];
-    [vc release];
 }
 
 @end
index 7c0748e..c512b37 100755 (executable)
                                                                <int key="IBUISystemItemIdentifier">5</int>
                                                        </object>
                                                        <object class="IBUIBarButtonItem" id="468797627">
+                                                               <object class="NSCustomResource" key="IBUIImage">
+                                                                       <string key="NSClassName">NSImage</string>
+                                                                       <string key="NSResourceName">HomeFolderIcon.png</string>
+                                                               </object>
                                                                <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
                                                                <float key="IBUIWidth">33</float>
                                                                <reference key="IBUIToolbar" ref="1006532466"/>
-                                                               <int key="IBUISystemItemIdentifier">6</int>
                                                        </object>
                                                </object>
                                        </object>
                                </object>
                                <object class="IBConnectionRecord">
                                        <object class="IBCocoaTouchEventConnection" key="connection">
+                                               <string key="label">homeButtonPressed:</string>
+                                               <reference key="source" ref="468797627"/>
+                                               <reference key="destination" ref="372490531"/>
+                                       </object>
+                                       <int key="connectionID">31</int>
+                               </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBCocoaTouchEventConnection" key="connection">
                                                <string key="label">refreshButtonPressed:</string>
                                                <reference key="source" ref="760150208"/>
                                                <reference key="destination" ref="372490531"/>
                                <reference key="dict.values" ref="0"/>
                        </object>
                        <nil key="sourceID"/>
-                       <int key="maxID">30</int>
+                       <int key="maxID">31</int>
                </object>
                <object class="IBClassDescriber" key="IBDocument.Classes">
                        <object class="NSMutableArray" key="referencedPartialClassDescriptions">
                                        <string key="className">ContainersViewController</string>
                                        <string key="superclassName">OpenStackViewController</string>
                                        <object class="NSMutableDictionary" key="actions">
-                                               <string key="NS.key.0">refreshButtonPressed:</string>
-                                               <string key="NS.object.0">id</string>
+                                               <bool key="EncodedWithXMLCoder">YES</bool>
+                                               <object class="NSArray" key="dict.sortedKeys">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>homeButtonPressed:</string>
+                                                       <string>refreshButtonPressed:</string>
+                                               </object>
+                                               <object class="NSArray" key="dict.values">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>id</string>
+                                                       <string>id</string>
+                                               </object>
                                        </object>
                                        <object class="NSMutableDictionary" key="actionInfosByName">
-                                               <string key="NS.key.0">refreshButtonPressed:</string>
-                                               <object class="IBActionInfo" key="NS.object.0">
-                                                       <string key="name">refreshButtonPressed:</string>
-                                                       <string key="candidateClassName">id</string>
+                                               <bool key="EncodedWithXMLCoder">YES</bool>
+                                               <object class="NSArray" key="dict.sortedKeys">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>homeButtonPressed:</string>
+                                                       <string>refreshButtonPressed:</string>
+                                               </object>
+                                               <object class="NSArray" key="dict.values">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <object class="IBActionInfo">
+                                                               <string key="name">homeButtonPressed:</string>
+                                                               <string key="candidateClassName">id</string>
+                                                       </object>
+                                                       <object class="IBActionInfo">
+                                                               <string key="name">refreshButtonPressed:</string>
+                                                               <string key="candidateClassName">id</string>
+                                                       </object>
                                                </object>
                                        </object>
                                        <object class="NSMutableDictionary" key="outlets">
                </object>
                <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
                <int key="IBDocument.defaultPropertyAccessControl">3</int>
+               <object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
+                       <string key="NS.key.0">HomeFolderIcon.png</string>
+                       <string key="NS.object.0">{22, 22}</string>
+               </object>
                <string key="IBCocoaTouchPluginVersion">1929</string>
        </data>
 </archive>
index 70772c4..6709684 100644 (file)
 @interface EditAccountGroupsViewController : UITableViewController <UITextFieldDelegate> {
     OpenStackAccount *account;
     NSString *groupName;
-    NSString *groupUsers;
     NSMutableDictionary *groups;
     NSMutableDictionary *metadata;
     
     BOOL removeGroupEnabled;
+    NSArray *groupUUIDs;
+    UITextField *groupNameTextField;
+    NSMutableDictionary *groupUsersTextFields;
+    NSInteger numberOfGroupUsersRows;
 }
 
 @property (nonatomic, retain) OpenStackAccount *account;
 @property (nonatomic, retain) NSString *groupName;
-@property (nonatomic, retain) NSString *groupUsers;
 @property (nonatomic, retain) NSMutableDictionary *groups;
 @property (nonatomic, retain) NSMutableDictionary *metadata;
-@property (nonatomic) BOOL removeGroupEnabled;
+
+@property (nonatomic, assign) BOOL removeGroupEnabled;
+@property (nonatomic, retain) NSArray *groupUUIDs;
+@property (nonatomic, retain) UITextField *groupNameTextField;
+@property (nonatomic, retain) NSMutableDictionary *groupUsersTextFields;
+@property (nonatomic, assign) NSInteger numberOfGroupUsersRows;
 
 @end
index 8fb2073..1fcee65 100644 (file)
 #import "OpenStackRequest.h"
 #import "APICallback.h"
 
-#define kGroupDetails 0 
-#define kSave 1
-#define kRemove 2
+#define kGroupName 0 
+#define kGroupUsers 1
+#define kSave 2
+#define kRemove 3
 
 @implementation EditAccountGroupsViewController
 
-@synthesize account;
-@synthesize groupName, groupUsers, removeGroupEnabled;
-@synthesize groups,metadata;
+@synthesize account, groupName, groups, metadata;
+@synthesize removeGroupEnabled, groupUUIDs, groupNameTextField, groupUsersTextFields, numberOfGroupUsersRows;
 
 #pragma mark - View lifecycle
 
     return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
 }
 
+- (void)viewDidLoad {
+    [super viewDidLoad];
+    self.groupUUIDs = [groups objectForKey:groupName];
+    if (!self.groupUUIDs) {
+        self.groupUUIDs = [NSArray array];
+        numberOfGroupUsersRows = 2;
+        self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:1];
+    } else {
+        numberOfGroupUsersRows = groupUUIDs.count + 1;
+        self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:groupUUIDs.count];
+    }
+}
+
 #pragma mark - Memory management
 
 - (void)dealloc {
-    [groupName release];
-    [groupUsers release];
     [account release];
-    [metadata release];
+    [groupName release];
     [groups release];
+    [metadata release];
+    [groupUUIDs release];
+    [groupNameTextField release];
+    [groupUsersTextFields release];
     [super dealloc];
 }
 
 #pragma mark - Internal
 
-- (BOOL)userInputIsValid:(NSString *)input fieldName:(NSString *)fieldName {
-    if (![input length] || ![[input stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) {
-        [self alert:@"Invalid input" message:[NSString stringWithFormat:@"%@ field cannot be empty", fieldName]];
+- (BOOL)groupNameIsValid:(NSString *)input {
+    if (!input.length) {
+        [self alert:@"Invalid input" message:@"Group name cannot be empty."];
         return NO;
-    }
-    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"= "];
-    if ([input rangeOfCharacterFromSet:set].location != NSNotFound) {
-        [self alert:@"Invalid input" message:[NSString stringWithFormat:@"%@ field cannot contain '=' or whitespace characters", fieldName]];
+    } else if ([input rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"=,;"]].location != NSNotFound) {
+        [self alert:@"Invalid input" message:@"Group name cannot contain '=', ',' or ';'."];
         return NO;
     }
     return YES;
 }
 
+- (NSMutableDictionary *)groupsInfoForGroups:(NSMutableDictionary *)groupsDictionary {
+    NSMutableDictionary *groupsInfo = [NSMutableDictionary dictionaryWithCapacity:groupsDictionary.count];
+    for (NSString *groupUser in groupsDictionary) {
+        [groupsInfo setObject:[[groupsDictionary objectForKey:groupUser] componentsJoinedByString:@","]
+                       forKey:groupUser];
+    }
+    return groupsInfo;
+}
+
 #pragma mark - UITableViewDataSource
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
-    if (removeGroupEnabled)
-        return 3;
-    else
-        return 2;
+    return (removeGroupEnabled ? 4 : 3);
 }
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    if (section == kGroupDetails)
-        return 2;
-    else
-        return 1;
+    if (section == kGroupUsers) {
+        return numberOfGroupUsersRows;
+    }
+    return 1;
+}
+
+- (UITextField *)textFieldForCell:(UITableViewCell *)cell {
+    CGRect rect = CGRectInset(cell.contentView.bounds, 10.0, 10.0);
+    UITextField *textField = [[[UITextField alloc] initWithFrame:rect] autorelease];
+    textField.frame = rect;
+    textField.clearButtonMode = UITextFieldViewModeWhileEditing;
+    textField.backgroundColor = [UIColor clearColor];
+    textField.opaque = YES;
+    textField.autocorrectionType = UITextAutocorrectionTypeNo;
+    textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
+    textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    return textField;
+}
+
+- (UITextField *)textFieldForCell:(UITableViewCell *)cell withIndexPath:(NSIndexPath *)indexPath {
+    if (indexPath.section == kGroupName) {
+        if (!groupNameTextField) {
+            self.groupNameTextField = [self textFieldForCell:cell];
+            groupNameTextField.placeholder = @"Group name";
+            groupNameTextField.text = groupName;
+            groupNameTextField.delegate = self;
+        }
+        return groupNameTextField;
+    } else if (indexPath.section == kGroupUsers) {
+        UITextField *groupUserTextField = [groupUsersTextFields objectForKey:indexPath];
+        if (!groupUserTextField) {
+            groupUserTextField = [self textFieldForCell:cell];
+            groupUserTextField.placeholder = @"User";
+            NSString *groupUser = (indexPath.row < groupUUIDs.count) ? [self.account displaynameForUUID:[groupUUIDs objectAtIndex:indexPath.row] safe:YES] : nil;
+            groupUserTextField.text = (groupUser && groupUser.length) ? groupUser : nil;
+            groupUserTextField.tag = indexPath.row;
+            [groupUsersTextFields setObject:groupUserTextField forKey:indexPath];
+            groupUserTextField.delegate = self;
+        }
+        return groupUserTextField;
+    }
+    return nil;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
-    static NSString *CellIdentifier = @"Cell";
+    if ((indexPath.section == kGroupName) || ((indexPath.section == kGroupUsers) && (indexPath.row < numberOfGroupUsersRows - 1))) {
+        static NSString *CellIdentifier = @"TextFieldCell";
+        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+        if (cell == nil) {
+            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+        }
+        cell.textLabel.text = nil;
+        cell.accessoryType = UITableViewCellAccessoryNone;
+        cell.selectionStyle = UITableViewCellSelectionStyleNone;
+        for (UIView *view in cell.contentView.subviews) {
+            [view removeFromSuperview];
+        }
+        UITextField *textField = [self textFieldForCell:cell withIndexPath:indexPath];
+        textField.returnKeyType = (((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 2)) ?
+                                   UIReturnKeyDefault : UIReturnKeyNext);
+        [cell.contentView addSubview:textField];
+        return cell;
+    }
     
+    static NSString *CellIdentifier = @"Cell";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil) {
         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
     }
-    
-    cell.textLabel.text = @"";
     cell.accessoryType = UITableViewCellAccessoryNone;
-    if (indexPath.section == kGroupDetails) {
-        UITextField *textField = nil;
-        for (id subView in cell.contentView.subviews) {
-            if ([subView isKindOfClass:[UITextField class]]) {
-                textField = (UITextField *)subView;
-            }
-        }
-        
-        if (textField == nil) {
-            CGRect bounds = [cell.contentView bounds];
-            CGRect rect = CGRectInset(bounds, 10.0, 10.0);                        
-            textField = [[UITextField alloc] initWithFrame:rect];
-            [textField setFrame:rect];
-        }
-        
-        [textField setClearButtonMode:UITextFieldViewModeWhileEditing];
-        [textField setBackgroundColor:[UIColor clearColor]];
-        [textField setOpaque:YES];
-        [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
-        [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
-        [textField setDelegate:self];
-        textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
 
-        if (indexPath.row == 0) {
-            textField.returnKeyType = UIReturnKeyNext;
-            textField.placeholder = @"Group name";
-            textField.text = self.groupName;
-            textField.tag = 0;
-        } else if (indexPath.row == 1) {
-            textField.returnKeyType = UIReturnKeyDefault;
-            textField.placeholder = @"Group users";
-            textField.text = self.groupUsers;
-            textField.tag = 1;
-        }
-        cell.selectionStyle = UITableViewCellSelectionStyleNone;
-        [cell.contentView addSubview:textField];
+    if ((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 1)) {
+        cell.textLabel.text = @"Add User";
+        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
     } else if (indexPath.section == kSave) {
-        cell.textLabel.text = @"Save";
+        cell.textLabel.text = @"Save Group";
+        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
     } else if (indexPath.section == kRemove) {
-        cell.textLabel.text = @"Remove";
+        cell.textLabel.text = @"Remove Group";
+        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
     }
     
     return cell;
 #pragma mark - UITableViewDelegate
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
-    NSIndexPath *keyCellIndexPath;
-    NSIndexPath *valueCellIndexPath;
-    UITableViewCell *cell;
-
-    if (indexPath.section != kGroupDetails)
+    if (((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 1)) ||
+        (indexPath.section == kSave) || (indexPath.section == kRemove))
         [self.view endEditing:YES];
-    
-    if (indexPath.section == kSave) {
-        keyCellIndexPath = [NSIndexPath indexPathForRow:0 inSection:kGroupDetails];
-        cell = [self.tableView cellForRowAtIndexPath:keyCellIndexPath];
-        UITextField *textField = [[cell.contentView subviews] objectAtIndex:0];
-        NSString *newGroupName = textField.text;
-        
-        if (![self userInputIsValid:newGroupName fieldName:@"Group name"]) {
-            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-            return;
-        }
 
-        valueCellIndexPath = [NSIndexPath indexPathForRow:1 inSection:kGroupDetails];
-        cell = [self.tableView cellForRowAtIndexPath:valueCellIndexPath];
-        textField = [[cell.contentView subviews] objectAtIndex:0];
-        NSString *newGroupUsers = textField.text;
-        
-        if (![self userInputIsValid:newGroupUsers fieldName:@"Group users"]) {
+    if ((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 1)) {
+        numberOfGroupUsersRows++;
+        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+        [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kGroupUsers] withRowAnimation:UITableViewRowAnimationNone];
+    } else if (indexPath.section == kSave) {
+        if (![self groupNameIsValid:groupNameTextField.text]) {
             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-            return;
+        } else {
+            NSMutableArray *newGroupUsers = [NSMutableArray arrayWithCapacity:groupUsersTextFields.count];
+            for (UITextField *groupUserTextField in [groupUsersTextFields allValues]) {
+                if (groupUserTextField.text && groupUserTextField.text.length)
+                    [newGroupUsers addObject:groupUserTextField.text];
+            }
+            if (!newGroupUsers.count) {
+                [self alert:@"Cannot save group" message:@"Please input at least one user."];
+                [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+            } else {
+                __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Checking users..."
+                                                                                                               andAddToView:self.view];
+                [[self.account.manager userCatalogForDisplaynames:newGroupUsers UUIDs:nil]
+                 success:^(OpenStackRequest *request) {
+                     NSDictionary *displaynameCatalog = [request displaynameCatalog];
+                     NSMutableArray *newGroupUUIDs = [NSMutableArray arrayWithCapacity:newGroupUsers.count];
+                     for (NSString *groupUser in newGroupUsers) {
+                         NSString *UUID = [displaynameCatalog objectForKey:groupUser];
+                         if (!UUID) {
+                             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+                             [self alert:@"Invalid user" message:[NSString stringWithFormat:@"User '%@' doesn't exist.", groupUser]];
+                             return;
+                         }
+                         [newGroupUUIDs addObject:UUID];
+                     }
+                     // Apply changes.
+                     [groups removeObjectForKey:groupName];
+                     [groups setObject:newGroupUUIDs forKey:groupNameTextField.text];
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Saving group..."
+                                                                                                                   andAddToView:self.view];
+                     [[self.account.manager writeAccountMetadata:[NSDictionary dictionaryWithObjectsAndKeys:
+                                                                  [self groupsInfoForGroups:groups], @"groups",
+                                                                  metadata, @"metadata",
+                                                                  nil]]
+                      success:^(OpenStackRequest *request) {
+                          self.groupName = groupNameTextField.text;
+                          self.groupUUIDs = newGroupUUIDs;
+                          numberOfGroupUsersRows = groupUUIDs.count + 1;
+                          self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:groupUUIDs.count];
+                          removeGroupEnabled = YES;
+                          self.navigationItem.title = @"Edit Group";
+                          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                          [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+                          [self.tableView reloadData];
+                     }
+                     failure:^(OpenStackRequest *request) {
+                         [groups removeObjectForKey:groupNameTextField.text];
+                         if (groupUUIDs.count) {
+                             [groups setObject:groupUUIDs forKey:groupName];
+                         }
+                         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                         [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+                         [self alert:@"There was a problem saving the group." request:request];
+                     }];
+                 }
+                 failure:^(OpenStackRequest *request) {
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                     [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+                     [self alert:@"There was a problem checking the users." request:request];
+                 }];
+            }
         }
-
-        [groups removeObjectForKey:groupName];
-        [groups setObject:newGroupUsers forKey:newGroupName];
-        NSDictionary *accountInfo = [NSDictionary dictionaryWithObjectsAndKeys:groups, @"groups",
-                                        metadata, @"metadata",
-                                        nil
-                                        ];
-        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Saving group..."
-                                                                                                       andAddToView:self.view];
-        [[self.account.manager writeAccountMetadata:accountInfo]
-         success:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             self.groupName = newGroupName;
-             self.groupUsers = newGroupUsers;
-             self.removeGroupEnabled = YES;
-             self.navigationItem.title = @"Edit Group";
-             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-             [self.tableView reloadData]; 
-         }
-         failure:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             [groups removeObjectForKey:newGroupName];
-             [groups setObject:self.groupUsers forKey:self.groupName];
-             [self.tableView reloadData];
-             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-             [self alert:@"There was a problem saving the group information." request:request];
-         }];
     } else if (indexPath.section == kRemove) {
         [groups removeObjectForKey:groupName];
-        NSDictionary *accountInfo = [NSDictionary dictionaryWithObjectsAndKeys:groups, @"groups",
-                                        metadata, @"metadata",
-                                        nil
-                                        ];
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Removing group..."
                                                                                                        andAddToView:self.view];
-        [[self.account.manager writeAccountMetadata:accountInfo]
+        [[self.account.manager writeAccountMetadata:[NSDictionary dictionaryWithObjectsAndKeys:
+                                                     [self groupsInfoForGroups:groups], @"groups",
+                                                     metadata, @"metadata",
+                                                     nil]]
          success:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
              self.groupName = @"";
-             self.groupUsers = @"";
-             self.removeGroupEnabled = NO;
-             [self.tableView reloadData];
+             self.groupUUIDs = [NSArray array];
+             numberOfGroupUsersRows = 2;
+             self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:1];
+             removeGroupEnabled = NO;
+             self.navigationItem.title = @"Add Group";
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+             [self.tableView reloadData];
          }
          failure:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             [groups setObject:groupUsers forKey:groupName];
+             if (groupUUIDs.count) {
+                 [groups setObject:groupUUIDs forKey:groupName];
+             }
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-             [self alert:@"There was a problem removing the group information." request:request];
+             [self alert:@"There was a problem removing the group." request:request];
          }];
     }
 }
 #pragma mark - UITextFieldDelegate
 
 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
-    if ([textField returnKeyType] == UIReturnKeyNext) {
-        [self userInputIsValid:textField.text fieldName:@"Group name"];
-
-        NSInteger nextTag = [textField tag] + 1;
-        UIView *nextTextField = [self.tableView viewWithTag:nextTag];
-        [nextTextField becomeFirstResponder];
-    } else {
-        [self userInputIsValid:textField.text fieldName:@"Group users"];
+    if (textField == groupNameTextField) {
+        if (![self groupNameIsValid:textField.text]) {
+            return NO;
+        }
+        [[groupUsersTextFields objectForKey:[NSIndexPath indexPathForRow:0 inSection:kGroupUsers]] becomeFirstResponder];
+    } else if (textField.tag == numberOfGroupUsersRows - 2) {
         [textField resignFirstResponder];
-    }
-    
+    } else {
+        [[groupUsersTextFields objectForKey:[NSIndexPath indexPathForRow:(textField.tag + 1) inSection:kGroupUsers]] becomeFirstResponder];
+    }    
     return YES;
 }
 
index fa52edc..77c132e 100644 (file)
@@ -91,7 +91,8 @@
          newFolder.sharing = folderViewController.folder.sharing;
          newFolder.metadata = object.metadata;
          [folderViewController.folder addFolder:newFolder];
-         [activityIndicatorView removeFromSuperview];
+         // XXX increase container.count if in rootFolder?
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [newFolder release];
          self.metadataKey = userInputMetaKey;
          self.metadataValue = userInputMetaValue;
          if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
              [self.folderViewController refreshButtonPressed:nil];
          } else {
-             self.folderViewController.needsRefreshing = YES;
              self.folderViewController.refreshWhenAppeared = YES;
          }
      }
      failure:^(OpenStackRequest *request) {
          [object.metadata removeObjectForKey:userInputMetaKey];
          [object.metadata setObject:metadataValue forKey:metadataKey];
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
          [self.tableView reloadData];
          [self alert:@"There was a problem saving the metadata." request:request];
                      self.metadataValue = userInputMetaValue;
                      removeMetadataEnabled = YES;
                      [self.tableView reloadData];
-                     [activityIndicatorView removeFromSuperview];
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                      [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
                      if (objectIsContainer) {
                          container.metadata = object.metadata;
                  failure:^(OpenStackRequest *request) { 
                      [object.metadata removeObjectForKey:userInputMetaKey];
                      [object.metadata setObject:metadataValue forKey:metadataKey];
-                     [activityIndicatorView removeFromSuperview];
+                     [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                      [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
                      [self alert:@"There was a problem saving the metadata." request:request];
                  }];
             [object.metadata removeObjectForKey:metadataKey];
             [[self.account.manager writeObjectMetadata:container object:object]
              success:^(OpenStackRequest *request) {
-                 [activityIndicatorView removeFromSuperview];
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
                  self.metadataKey = @"";
                  self.metadataValue = @"";
              }
              failure:^(OpenStackRequest *request) {
                  [object.metadata setObject:metadataValue forKey:metadataKey];
-                 [activityIndicatorView removeFromSuperview];
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
                  [self alert:@"There was a problem saving the metadata." request:request];
 
index 3af698a..dd561b5 100644 (file)
 // interpreted as representing official policies, either expressed
 // or implied, of GRNET S.A.
 
-#import <UIKit/UIKit.h>
-
-@class FolderViewController, OpenStackRequest, OpenStackAccount, Container, StorageObject;
+@class FolderViewController, OpenStackAccount, Container, StorageObject;
 
 @interface EditPermissionsViewController : UITableViewController <UITextFieldDelegate, UIAlertViewDelegate> {
-    NSString *user;
-    BOOL readPermissionSelected;
-    BOOL writePermissionSelected;
-    NSMutableDictionary *permissions;
-    
     OpenStackAccount *account;
     Container *container;
     StorageObject *object;
-    
-    NSString *oldPermissionsString;
-    BOOL newPermissionsEntry;
+
+    NSString *permissionUser;
+    BOOL readPermissionSelected;
+    BOOL writePermissionSelected;
+    NSMutableDictionary *permissions;
+    NSString *currentUUID;
+    NSString *oldSharing;
     BOOL removePermissionsEnabled;
     BOOL objectIsFolder;
+    
+    UITextField *userTextField;
+    UITextField *groupTextField;
+    UITableViewCell *readPermissionsCell;
+    UITableViewCell *writePermissionsCell;
 }
 
 @property (nonatomic, retain) OpenStackAccount *account;
 @property (nonatomic, retain) Container *container;
 @property (nonatomic, retain) StorageObject *object;
+@property (nonatomic, assign) FolderViewController *folderViewController;
 
-@property (nonatomic, retain) NSString *user;
-@property (nonatomic) BOOL readPermissionSelected;
-@property (nonatomic) BOOL writePermissionSelected;
-@property (nonatomic, assign) BOOL newPermissionsEntry;
-@property (nonatomic, assign) BOOL removePermissionsEnabled;
+@property (nonatomic, retain) NSString *permissionUser;
+@property (nonatomic, assign) BOOL readPermissionSelected;
+@property (nonatomic, assign) BOOL writePermissionSelected;
 @property (nonatomic, retain) NSMutableDictionary *permissions;
-@property (nonatomic, retain) NSString *oldPermissionsString;
-@property (nonatomic, assign) FolderViewController *folderViewController; 
+@property (nonatomic, retain) NSString *currentUUID;
+@property (nonatomic, retain) NSString *oldSharing;
+@property (nonatomic, assign) BOOL removePermissionsEnabled;
 @property (nonatomic, assign) BOOL objectIsFolder;
 
+@property (nonatomic, retain) UITextField *userTextField;
+@property (nonatomic, retain) UITextField *groupTextField;
+@property (nonatomic, retain) UITableViewCell *readPermissionsCell;
+@property (nonatomic, retain) UITableViewCell *writePermissionsCell;
+
 @end
index 6822fae..9979361 100644 (file)
 
 @implementation EditPermissionsViewController
 
-@synthesize account, container, object;
-@synthesize user, readPermissionSelected, writePermissionSelected, permissions;
-@synthesize oldPermissionsString;
-@synthesize newPermissionsEntry, removePermissionsEnabled;
-@synthesize folderViewController, objectIsFolder;
+@synthesize account, container, object, folderViewController;
+@synthesize permissionUser, readPermissionSelected, writePermissionSelected, permissions;
+@synthesize currentUUID;
+@synthesize oldSharing, removePermissionsEnabled, objectIsFolder;
+@synthesize userTextField, groupTextField, readPermissionsCell, writePermissionsCell;
+
+#pragma mark - View lifecycle
+
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
+    return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
+}
+
+#pragma mark - Memory management
 
 - (void)dealloc {
     [account release];
     [container release];
     [object release];
-    [user release];
+    [permissionUser release];
     [permissions release];
-    [oldPermissionsString release];
+    [oldSharing release];
+    [currentUUID release];
+    [userTextField release];
+    [groupTextField release];
+    [readPermissionsCell release];
+    [writePermissionsCell release];
     [super dealloc];
 }
 
-#pragma mark - View lifecycle
-
-- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
-    return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
-}
-
 #pragma mark - Internal
 
-- (NSString *)buildPermissionsString {
-    NSString *readPermissionsString = @"";
-    NSString *writePermissionsString = @"";
-    for (NSString *aUser in [permissions allKeys]) {
-        if ([[permissions objectForKey:aUser] isEqual:@"read"]) {
-            if (!readPermissionsString.length) {
-                readPermissionsString = [NSString stringWithFormat:@"read=%@", aUser];
-            } else {
-                readPermissionsString = [NSString stringWithFormat:@"%@,%@", readPermissionsString ,aUser];
-            }
-        } else if ([[permissions objectForKey:aUser] isEqual:@"write"]) {
-            if (!writePermissionsString.length) {
-                writePermissionsString = [NSString stringWithFormat:@"write=%@", aUser];
-            } else {
-                writePermissionsString = [NSString stringWithFormat:@"%@,%@", writePermissionsString, aUser];
-            }
-        }
+- (BOOL)userIsValid:(NSString *)input {
+    if (!input.length) {
+        [self alert:@"Invalid input" message:@"User cannot be empty."];
+        return NO;
     }
-    if (!writePermissionsString.length)
-        return readPermissionsString;
-    if (!readPermissionsString.length)
-        return writePermissionsString;
-    return [NSString stringWithFormat:@"%@;%@", readPermissionsString, writePermissionsString];
+    return YES;
 }
 
-- (BOOL)userInputIsValid:(NSString *)input {
-    if (!input.length || ![input stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length) {
-        [self alert:@"Invalid input" message:@"User name cannot be empty"];
-        return NO;
-    }
-    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"=,;"];
-    if ([input rangeOfCharacterFromSet:set].location != NSNotFound) {
-        [self alert:@"Invalid input" message:@"User name cannot contain '=', ',' or ';'"];
+- (BOOL)groupIsValid:(NSString *)input {
+    if (input && ([input rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"=,;"]].location != NSNotFound)) {
+        [self alert:@"Invalid input" message:@"Group cannot contain '=', ',' or ';'."];
         return NO;
     }
     return YES;
 }
 
-- (void)createNewFolder {
+- (void)applyPermissions {
+    [permissions removeObjectForKey:permissionUser];
+    NSString *newPermissionUser = ((groupTextField.text && groupTextField.text.length) ?
+                                   [NSString stringWithFormat:@"%@:%@", currentUUID, groupTextField.text] :
+                                   currentUUID);
+    [permissions setObject:(writePermissionSelected ? @"write" : @"read") forKey:newPermissionUser];
+    self.oldSharing = object.sharing;
+    [object setPermissions:permissions];
     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Applying permissions..."
                                                                                                    andAddToView:self.view];
+    [[self.account.manager writeObjectMetadata:container object:object]
+     success:^(OpenStackRequest *request) {
+         self.permissionUser = newPermissionUser;
+
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
+         if (!removePermissionsEnabled) {
+             removePermissionsEnabled = YES;
+             self.navigationItem.title = @"Edit Permission";
+             [self.tableView reloadData];
+         }
+         
+         if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
+             if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
+                 [self.folderViewController refreshButtonPressed:nil];
+             } else {
+                 self.folderViewController.refreshWhenAppeared = YES;
+             }
+         }
+     }
+     failure:^(OpenStackRequest *request) {
+         object.sharing = self.oldSharing;
+         self.permissions = object.permissions;
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
+         [self alert:@"There was a problem applying the permissions." request:request];
+     }];    
+}
+
+- (void)createNewFolder {
+    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Creating folder..."
+                                                                                                   andAddToView:self.view];
+    object.contentType = @"application/directory";
+    object.data = [NSData data];
     [[self.account.manager writeObject:self.container object:object downloadProgressDelegate:nil]
      success:^(OpenStackRequest *request) {
          Folder *newFolder = [[Folder alloc] init];
          newFolder.parent = folderViewController.folder;
          newFolder.sharing = folderViewController.folder.sharing;
          [folderViewController.folder addFolder:newFolder];
-         [activityIndicatorView removeFromSuperview];
+         // XXX increase container.count if in rootFolder?
          [newFolder release];
-         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
-         removePermissionsEnabled = YES;
-         [self.tableView reloadData];
+         
          if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
              [self.folderViewController refreshButtonPressed:nil];
          } else {
-             self.folderViewController.needsRefreshing = YES;
              self.folderViewController.refreshWhenAppeared = YES;
          }
+         [self applyPermissions];
      }
      failure:^(OpenStackRequest *request) {
-         object.sharing = self.oldPermissionsString;
-         readPermissionSelected = !readPermissionSelected;
-         writePermissionSelected = !writePermissionSelected;
-         
-         if (readPermissionSelected)
-             [self.permissions setObject:@"read" forKey:user];
-         else if (writePermissionSelected)
-             [self.permissions setObject:@"write" forKey:user];
-         
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
-         [self.tableView reloadData];
-         [self alert:@"There was a problem applying the permissions." request:request];
+         [self alert:@"There was a problem creating the folder." request:request];
      }];
 }
 
-#pragma mark - Table view data source
+
+#pragma mark - UITableViewDataSource
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
     return (removePermissionsEnabled ? 4 : 3);
 }
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
-    return ((section == kPermissions) ? 2 : 1);
+    if ((section == kUser) || (section == kPermissions)) {
+        return 2;
+    }
+    return 1;
+}
+
+- (UITextField *)textFieldForCell:(UITableViewCell *)cell {
+    CGRect rect = CGRectInset(cell.contentView.bounds, 10.0, 10.0);
+    UITextField *textField = [[[UITextField alloc] initWithFrame:rect] autorelease];
+    textField.frame = rect;
+    textField.clearButtonMode = UITextFieldViewModeWhileEditing;
+    textField.backgroundColor = [UIColor clearColor];
+    textField.opaque = YES;
+    textField.autocorrectionType = UITextAutocorrectionTypeNo;
+    textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
+    textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
+    return textField;
+}
+
+- (UITextField *)textFieldForCell:(UITableViewCell *)cell withIndexPath:(NSIndexPath *)indexPath {
+    if (indexPath.row == 0) {
+        if (!userTextField) {
+            self.userTextField = [self textFieldForCell:cell];
+            userTextField.returnKeyType = UIReturnKeyNext;
+            userTextField.placeholder = @"User";
+            NSRange rangeOfColumn = [permissionUser rangeOfString:@":"];
+            userTextField.text = [self.account displaynameForUUID:((rangeOfColumn.location == NSNotFound) ? permissionUser : [permissionUser substringToIndex:rangeOfColumn.location])
+                                                             safe:YES];
+            userTextField.delegate = self;
+        }
+        return userTextField;
+    } else if (indexPath.row == 1) {
+        if (!groupTextField) {
+            self.groupTextField = [self textFieldForCell:cell];
+            groupTextField.returnKeyType = UIReturnKeyDefault;
+            groupTextField.placeholder = @"Group (optional)";
+            NSRange rangeOfColumn = [permissionUser rangeOfString:@":"];
+            groupTextField.text = ((rangeOfColumn.location == NSNotFound) || (rangeOfColumn.location == permissionUser.length - 1)) ? nil : [permissionUser substringFromIndex:(rangeOfColumn.location + 1)];
+            groupTextField.delegate = self;
+        }
+        return groupTextField;
+    }
+    return nil;
 }
 
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
-    static NSString *CellIdentifier = @"Cell";
+    if (indexPath.section == kUser) {
+        static NSString *CellIdentifier = @"TextFieldCell";
+        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
+        if (cell == nil) {
+            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
+        }
+        cell.textLabel.text = nil;
+        cell.accessoryType = UITableViewCellAccessoryNone;
+        cell.selectionStyle = UITableViewCellSelectionStyleNone;
+        for (UIView *view in cell.contentView.subviews) {
+            [view removeFromSuperview];
+        }
+        [cell.contentView addSubview:[self textFieldForCell:cell withIndexPath:indexPath]];
+        return cell;
+    }
     
+    static NSString *CellIdentifier = @"Cell";
     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
     if (cell == nil) {
         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
     }
     
-    if (indexPath.section == kUser) {
-        UITextField *textField = nil;
-        for (id subView in cell.contentView.subviews) {
-            if ([subView isKindOfClass:[UITextField class]]) {
-                textField = (UITextField *)subView;
-            }
-        }
-        
-        if (textField == nil) {
-            CGRect bounds = [cell.contentView bounds];
-            CGRect rect = CGRectInset(bounds, 10.0, 10.0);                        
-            textField = [[UITextField alloc] initWithFrame:rect];
-            [textField setFrame:rect];
-        }
-        
-        [textField setClearButtonMode:UITextFieldViewModeWhileEditing];
-        [textField setBackgroundColor:[UIColor clearColor]];
-        [textField setOpaque:YES];
-        [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
-        [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
-        [textField setDelegate:self];
-        textField.placeholder = @"User or User:Group";
-        textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
-        textField.text = self.user;
-        
-        cell.selectionStyle = UITableViewCellSelectionStyleNone;
-        [cell.contentView addSubview:textField];
-        cell.selectionStyle = UITableViewCellSelectionStyleNone;
-    } else if (indexPath.section == kPermissions) {
-        cell.selectionStyle = UITableViewCellSelectionStyleNone;
+    if (indexPath.section == kPermissions) {
         if (indexPath.row == 0) {
             cell.textLabel.text = @"Read Only";
-            if (readPermissionSelected)
-                cell.accessoryType = UITableViewCellAccessoryCheckmark;
-            else
-                cell.accessoryType = UITableViewCellAccessoryNone;
+            cell.accessoryType = (readPermissionSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone);
+            self.readPermissionsCell = cell;
         } else {
             cell.textLabel.text = @"Read/Write";
-            if (writePermissionSelected)
-                cell.accessoryType = UITableViewCellAccessoryCheckmark;
-            else
-                cell.accessoryType = UITableViewCellAccessoryNone;
+            cell.accessoryType = (writePermissionSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone);
+            self.writePermissionsCell = cell;
         }
+        cell.selectionStyle = UITableViewCellSelectionStyleNone;
     } else if (indexPath.section == kSavePermissions) {
         cell.textLabel.text = @"Save";
         cell.accessoryType = UITableViewCellAccessoryNone;
     return cell;
 }
 
-#pragma mark - Table view delegate
+#pragma mark - UITableViewDelegate
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     if (indexPath.section != kUser)
         [self.view endEditing:YES];
     
-    NSString *newUserName;
-    NSIndexPath *userCellIndexPath = [NSIndexPath indexPathForRow:0 inSection:kUser];
-    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:userCellIndexPath];
-    UITextField *textField = [[cell.contentView subviews] objectAtIndex:0];
-    newUserName = textField.text;
-    if (![self userInputIsValid:newUserName]) {
-        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-    } else if (indexPath.section == kPermissions) {
-        UITableViewCell *readPermissionsCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:kPermissions]];
-        UITableViewCell *writePermissionsCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:kPermissions]];
-
-        if (indexPath.row == 0) {
-            if (!readPermissionSelected) {
-                readPermissionSelected = YES;
-                if (writePermissionSelected) {
-                    writePermissionSelected = FALSE;
-                    writePermissionsCell.accessoryType = UITableViewCellAccessoryNone;
-                }
-                readPermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
+    if (indexPath.section == kPermissions) {
+        if ((indexPath.row == 0) && !readPermissionSelected) {
+            readPermissionSelected = YES;
+            if (writePermissionSelected) {
+                writePermissionSelected = FALSE;
+                writePermissionsCell.accessoryType = UITableViewCellAccessoryNone;
             }
-        } else if (indexPath.row == 1) {
-            if (!writePermissionSelected) {
-                writePermissionSelected = YES;
-                if (readPermissionSelected) {
-                    readPermissionSelected = FALSE;
-                    readPermissionsCell.accessoryType = UITableViewCellAccessoryNone;
-                }
-                writePermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
+            readPermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
+        } else if ((indexPath.row == 1) && !writePermissionSelected) {
+            writePermissionSelected = YES;
+            if (readPermissionSelected) {
+                readPermissionSelected = FALSE;
+                readPermissionsCell.accessoryType = UITableViewCellAccessoryNone;
             }
+            writePermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
         }
     } else if (indexPath.section == kSavePermissions) {
-        if (!readPermissionSelected && !writePermissionSelected) {
-            [self alert:@"Cannot save permissions"  message:@"Please select permissions type"];
+        if (![self userIsValid:userTextField.text] || ![self groupIsValid:groupTextField.text]) {
+            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+        } else if (!readPermissionSelected && !writePermissionSelected) {
+            [self alert:@"Cannot save permissions"  message:@"Please select a permission type."];
             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
         } else {
-            [self.permissions removeObjectForKey:self.user];
-            if (readPermissionSelected) 
-                [self.permissions setObject:@"read" forKey:newUserName];
-            else if (writePermissionSelected)
-                [self.permissions setObject:@"write" forKey:newUserName];
-        
-            self.user = newUserName;
-            self.oldPermissionsString = object.sharing;
-            object.sharing = [self buildPermissionsString];
-        
-            if (objectIsFolder && ![PithosUtilities isContentTypeDirectory:object.contentType]) {
-                if ((([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) &&
-                    [folderViewController.parentFolderViewController.folder.objects objectForKey:object.name]) || 
-                    (([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) &&
-                     [folderViewController.folder.objects objectForKey:object.name])) {
-                    NSString *alertMessage = [NSString stringWithFormat:@"In order to apply the changes in '%@', the object at the same path must be replaced. Continue?",object.name];
-                    NSString *alertTitle = @"Apply changes";
-                    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertTitle
-                                                                    message:alertMessage
-                                                                   delegate:self
-                                                          cancelButtonTitle:@"Cancel"
-                                                          otherButtonTitles:@"OK", nil];
-                    [alert show];
-                    [alert release];
-                } else {
-                    object.name = object.fullPath;
-                    object.contentType = @"application/directory";
-                    object.data = [NSData data];
-                    [self createNewFolder];  
-                }
-            } else {
-                __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Applying permissions..."
-                                                                                                               andAddToView:self.view];
-                [[self.account.manager writeObjectMetadata:container object:object]
-                 success:^(OpenStackRequest *request) {
-                     [activityIndicatorView removeFromSuperview];
-                     [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-                     removePermissionsEnabled = YES;
-                     [self.tableView reloadData];
-                     if (objectIsFolder || (account.shared && ![oldPermissionsString isEqualToString:object.sharing])) {
-                         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-                             [self.folderViewController refreshButtonPressed:nil];
-                         }
-                         else {
-                             self.folderViewController.needsRefreshing = YES;
+            __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Checking user..."
+                                                                                                           andAddToView:self.view];
+            [[self.account.manager userCatalogForDisplaynames:[NSArray arrayWithObject:userTextField.text] UUIDs:nil]
+             success:^(OpenStackRequest *request) {
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                 self.currentUUID = [[request displaynameCatalog] objectForKey:userTextField.text];
+                 if (currentUUID) {
+                     if (objectIsFolder && ![PithosUtilities isContentTypeDirectory:object.contentType]) {
+                         if ((([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) &&
+                              [folderViewController.parentFolderViewController.folder.objects objectForKey:object.name]) ||
+                             (([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) &&
+                              [folderViewController.folder.objects objectForKey:object.name])) {
+                             UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Apply changes"
+                                                                              message:[NSString stringWithFormat:@"In order to apply the changes to folder '%@', the file at the same path must be replaced. Continue?", object.name]
+                                                                             delegate:self
+                                                                    cancelButtonTitle:@"Cancel"
+                                                                    otherButtonTitles:@"OK", nil] autorelease];
+                             [alert show];
+                         } else {
+                             [self createNewFolder];
                          }
-                     } 
-                 }
-                 failure:^(OpenStackRequest *request) {
-                     object.sharing = self.oldPermissionsString;
-                     readPermissionSelected = !readPermissionSelected;
-                     writePermissionSelected = !writePermissionSelected;
-                     
-                     if (readPermissionSelected)
-                         [self.permissions setObject:@"read" forKey:user];
-                     else if (writePermissionSelected) 
-                         [self.permissions setObject:@"write" forKey:user];
-                     
-                     [activityIndicatorView removeFromSuperview];
+                     } else {
+                         [self applyPermissions];
+                     }
+                 } else {
                      [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-                     [self.tableView reloadData];
-                     [self alert:@"There was a problem applying the permissions." request:request];
-                 }];
-            }
+                     [self alert:@"Invalid user" message:[NSString stringWithFormat:@"User '%@' doesn't exist.", userTextField.text]];
+                 }
+             }
+             failure:^(OpenStackRequest *request) {
+                 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+                 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+                 [self alert:@"There was a problem checking the user." request:request];
+             }];
         }
     } else if (indexPath.section == kRemovePermissions) {
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Removing permissions..."
                                                                                                        andAddToView:self.view];
-        [permissions removeObjectForKey:user];
-        self.oldPermissionsString = object.sharing;
-        object.sharing = [self buildPermissionsString];
+        [permissions removeObjectForKey:permissionUser];
+        self.oldSharing = object.sharing;
+        [object setPermissions:permissions];
         [[self.account.manager writeObjectMetadata:container object:object] 
          success:^(OpenStackRequest *request) {
-             self.user = @"";
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
+             self.permissionUser = @"";
              self.readPermissionSelected = FALSE;
              self.writePermissionSelected = FALSE;
              removePermissionsEnabled = NO;
+             self.navigationItem.title = @"Add Permission";
              [self.tableView reloadData];
-             [activityIndicatorView removeFromSuperview];
-             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-             if (objectIsFolder || (account.shared && ![oldPermissionsString isEqualToString:object.sharing])) {
-                 self.folderViewController.needsRefreshing = YES;
-                 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
+
+             if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
+                 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
                      [self.folderViewController refreshButtonPressed:nil];
-                 else
+                 } else {
                      self.folderViewController.refreshWhenAppeared = YES;
+                 }
              }
          }
          failure:^(OpenStackRequest *request) {
-             object.sharing = self.oldPermissionsString;
-             if (readPermissionSelected)
-                 [self.permissions setObject:@"read" forKey:user];
-             else if (writePermissionSelected) 
-                 [self.permissions setObject:@"write" forKey:user];
-             
-             [activityIndicatorView removeFromSuperview];
+             object.sharing = self.oldSharing;
+             self.permissions = object.permissions;
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
-             [self.tableView reloadData];
              [self alert:@"There was a problem removing the permissions." request:request];
          }];
     }
 }
 
-#pragma mark - Textfield delegate
+#pragma mark - UITextFieldDelegate
 
 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
-    [self userInputIsValid:textField.text];
-    [textField resignFirstResponder];
+    if (textField == userTextField) {
+        if (![self userIsValid:textField.text]) {
+            return NO;
+        }
+        [groupTextField becomeFirstResponder];
+    } else if (textField == groupTextField) {
+        if (![self groupIsValid:textField.text]) {
+            return NO;
+        }
+        [groupTextField resignFirstResponder];
+    }
     return YES;
 }
 
-#pragma mark - Alertview delegate
+#pragma mark - UIAlertViewDelegate
 
 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
     if (buttonIndex == 1) {
-        object.name = object.fullPath;
-        object.contentType = @"application/directory";
-        object.data = [NSData data];
         [self createNewFolder];
     } else {
         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
index 698df32..3395af5 100644 (file)
              [self.tableView reloadData];
              if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
                  [self.containerDetailViewController.containersViewController refreshButtonPressed:nil];
-             [activityIndicatorView removeFromSuperview];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
          }
          failure:^(OpenStackRequest *request) {
              [self configVersioningVariables];
              [self.tableView reloadData];
              
-             [activityIndicatorView removeFromSuperview];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
              [self.tableView reloadData];
              [self alert:@"There was a problem applying the policy." request:request];
index 9e486cd..2fcd2df 100755 (executable)
@@ -24,7 +24,9 @@
         // "Details" button
         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
             self.logEntryModalViewController.modalPresentationStyle = UIModalPresentationFormSheet;
-        }                
+        }
+        // XXX If the viewController is removed from the windows hierarchy, for a late request e.g.,
+        // this will do nothing and result in a warning.
         [self.viewController presentModalViewController:self.logEntryModalViewController animated:YES];
     } else if (buttonIndex == 2) {
         // "Account Settings" button
index fa5369b..fc4b1c4 100755 (executable)
@@ -22,8 +22,8 @@
 - (id)initWithObject:(StorageObject *)anObject {
     if ((self = [super init])) {
         self.folderObject = anObject;
-        self.objects = [NSMutableDictionary dictionary];
-        self.folders = [NSMutableDictionary dictionary];
+        objects = [[NSMutableDictionary alloc] init];
+        folders = [[NSMutableDictionary alloc] init];
     }
     return self;
 }
 
 - (void)setObjects:(NSMutableDictionary *)objs {
     if (![objects isEqualToDictionary:objs]) {
-        [objects release];
         NSMutableDictionary *folderedFiles = [NSMutableDictionary dictionary];
         NSMutableDictionary *files = [NSMutableDictionary dictionary];
         self.folders = [NSMutableDictionary dictionary];
         
         // take the foldered files and recursively build the rest of the folder structure
         for (NSString *folderName in [folderedFiles allKeys]) {
-            Folder *folder;
-            StorageObject *object = [objs objectForKey:folderName];
-            if (object && [PithosUtilities isContentTypeDirectory:object.contentType]) {
-                folder = [Folder folderWithObject:object];
-            } else {
+            Folder *folder = [folders objectForKey:folderName];
+            if (!folder) {
                 folder = [Folder folder];
                 folder.name = folderName;
+                [self.folders setObject:folder forKey:folderName];
                 folder.metadata = [NSMutableDictionary dictionary];
             }
             folder.parent = self;
             folder.objects = [folderedFiles objectForKey:folderName];
-            [self.folders setObject:folder forKey:folder.name];
         }
         
+        [objects release];
         objects = [files retain];
         [sortedContentsByName release];
         sortedContentsByName = nil;
index ca255c8..913243d 100644 (file)
@@ -52,6 +52,7 @@
 @property (nonatomic, retain) Container *container;
 @property (nonatomic, retain) Folder *folder;
 @property (nonatomic, retain) FolderViewController *folderViewController;
+@property (nonatomic, retain) NSMutableDictionary *permissions;
 
 - (void)reloadMetadataSection;
 
index 97e15b3..ce0be16 100644 (file)
 
 @implementation FolderDetailViewController
 
-@synthesize account, container, folder, folderViewController;
+@synthesize account, container, folder, folderViewController, permissions;
 
-#pragma mark - 
-#pragma mark Memory management
+#pragma mark - View lifecycle
 
-- (void)dealloc {
-    [object release];
-    [account release];
-    [container release];
-    [folderViewController release];
-    [folder release];
-    [permissions release];
-    [super dealloc];
+- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
+    return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
 }
 
-#pragma mark - View lifecycle
-
 - (void)viewDidLoad {
     [super viewDidLoad];
-    permissions = [[NSMutableDictionary alloc] init];
-    if (folder.sharing.length > 0) {
-        NSArray *sharingArray = [folder.sharing componentsSeparatedByString:@";"];
-        for (NSString *typeSpecificPermissions in sharingArray) { 
-            NSArray *array=[typeSpecificPermissions componentsSeparatedByString:@"="];
-            NSString *permissionsType = [array objectAtIndex:0];
-            if ([permissionsType hasPrefix:@" "])
-                permissionsType = [permissionsType substringFromIndex:1];
-            
-            NSArray *users = [[array objectAtIndex:1] componentsSeparatedByString:@","];
-            for (NSString *user in users) {
-                [permissions setObject:permissionsType forKey:user];
-            }
-        }
-    }
-    
+    self.permissions = [folder.folderObject permissions];
     folderIsReadOnly = NO;
-    if (account.sharingAccount) { 
-        if ([permissions count] > 0) {
-            folderIsReadOnly = [[permissions objectForKey:[account username]] isEqualToString:@"read"];
-        }
+    if (account.sharingAccount && permissions.count) {
+        folderIsReadOnly = ![[permissions objectForKey:account.username] isEqualToString:@"write"];
     }
     
     object = [[StorageObject alloc] init];
     [super viewDidAppear:animated];
     if (folder.metadata == nil) {
         [self reloadMetadataSection];
+    } else {
+        [self updatePermissionsUserCatalog];
     }
 }
 
     folder.sharing = object.sharing;
 }
 
-- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
-    return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
+#pragma mark - Memory management
+
+- (void)dealloc {
+    [object release];
+    [account release];
+    [container release];
+    [folderViewController release];
+    [folder release];
+    [permissions release];
+    [super dealloc];
 }
 
-#pragma mark - Table view data source
+#pragma mark - Internal
+
+- (void)updatePermissionsUserCatalog {
+    NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:permissions.count];
+    for (NSString *user in [permissions allKeys]) {
+        NSRange rangeOfColumn = [user rangeOfString:@":"];
+        NSString *UUID = (rangeOfColumn.location == NSNotFound) ? user : [user substringToIndex:rangeOfColumn.location];
+        if (![UUID isEqualToString:@"*"]) {
+            [UUIDs addObject:UUID];
+        }
+    }
+    if (UUIDs.count) {
+        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading permissions..."
+                                                                                                       andAddToView:self.view];
+        [[self.account.manager userCatalogForDisplaynames:nil UUIDs:UUIDs]
+         success:^(OpenStackRequest *request) {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kPermissions] withRowAnimation:UITableViewRowAnimationNone];
+         }
+         failure:^(OpenStackRequest *request) {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         }];
+    }
+}
+#pragma mark - Actions
+
+- (void)reloadMetadataSection {
+    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading metadata..."
+                                                                                                   andAddToView:self.view];
+    [[self.account.manager getObjectInfo:container object:object version:nil]
+     success:^(OpenStackRequest *request) {
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         folder.metadata = [NSMutableDictionary dictionary];
+         for (NSString *header in request.responseHeaders) {
+             NSString *metadataKey;
+             NSString *metadataValue;
+             if ([header rangeOfString:@"X-Object-Meta-"].location != NSNotFound) {
+                 metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:14]];
+                 metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
+                 [folder.metadata setObject:metadataValue forKey:metadataKey];
+             }
+         }
+         object.metadata = folder.metadata;
+         [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kMetadata] withRowAnimation:UITableViewRowAnimationFade];
+         [self updatePermissionsUserCatalog];
+     }
+     failure:^(OpenStackRequest *request) {
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+         [self alert:@"There was a problem retrieving the object's metadata." request:request];
+     }];
+}
+
+#pragma mark - UITableViewDataSource
 
 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
     return 3;
         return (folder.lastModifiedString ? 3 : 2);
     } else if (section == kPermissions) {
         if (account.sharingAccount) {
-            return [permissions count];
+            return permissions.count;
         } else {
-            return 1 + [permissions count];
+            return (permissions.count + 1);
         }
     } else if (section == kMetadata) {
         if (folderIsReadOnly) {
             return [folder.metadata count];
         } else {
-            return 1 + [folder.metadata count];
+            return ([folder.metadata count] + 1);
         }
     }
     return 0;
     return MAX(tableView.rowHeight, result);
 }
 
-
 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     static NSString *CellIdentifier = @"Cell";
     
             cell.accessoryType = UITableViewCellAccessoryNone;
             cell.selectionStyle = UITableViewCellSelectionStyleNone;
             cell.userInteractionEnabled = NO;
-        }
-        else {
+        } else {
             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
         }
         cell.accessoryView = nil;
         
-        if (indexPath.row == [permissions count]) {
+        if (indexPath.row == permissions.count) {
             cell.textLabel.text = @"Share";
             cell.detailTextLabel.text = @""; 
-        }
-        else {
-            NSString *user = [[permissions allKeys] objectAtIndex:indexPath.row];
-            cell.textLabel.text = user;
-            cell.detailTextLabel.text = [permissions objectForKey:user];
+        } else {
+            NSString *user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
+            NSRange rangeOfColumn = [user rangeOfString:@":"];
+            NSString *UUID = (rangeOfColumn.location == NSNotFound) ? user : [user substringToIndex:rangeOfColumn.location];
+            NSString *group = ((rangeOfColumn.location == NSNotFound) || (rangeOfColumn.location == user.length - 1)) ? nil : [user substringFromIndex:(rangeOfColumn.location + 1)];
+            NSMutableString *displayname = [NSMutableString stringWithString:[account displaynameForUUID:UUID safe:YES]];
+            if (group) {
+                [displayname appendFormat:@":%@", group];
+            }
+            cell.textLabel.text = displayname;
+            
+            cell.detailTextLabel.numberOfLines = 1;
+            cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
+            cell.detailTextLabel.text = ([[permissions objectForKey:user] isEqualToString:@"write"] ? @"Read/Write" : @"Read Only");
         }
     }
 
     return cell;
 }
 
-
-#pragma mark - Table view delegate
+#pragma mark - UITableViewDelegate
 
 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
     if (indexPath.section == kMetadata) {
         EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
         NSString *user;
         
-        if (indexPath.row == [permissions count]) {
+        if (indexPath.row == permissions.count) {
             user = @"";
             vc.removePermissionsEnabled = NO;
-            vc.navigationItem.title = @"Share";
+            vc.navigationItem.title = @"Add Permission";
         } else {
-            user = [[permissions allKeys] objectAtIndex:indexPath.row];
+            user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
             NSString *userPermissions = [permissions objectForKey:user];
-            if ([userPermissions rangeOfString:@"read"].location != NSNotFound)
+            if ([userPermissions rangeOfString:@"read"].location != NSNotFound) {
                 vc.readPermissionSelected = YES;
-            else
+            } else {
                 vc.readPermissionSelected = NO;
-            
-            if ([userPermissions rangeOfString:@"write"].location != NSNotFound)
+            }
+            if ([userPermissions rangeOfString:@"write"].location != NSNotFound) {
                 vc.writePermissionSelected = YES;
-            else
+            } else {
                 vc.writePermissionSelected = NO;
-            
+            }
             vc.removePermissionsEnabled = YES;
-            vc.navigationItem.title = @"Edit Sharing";
+            vc.navigationItem.title = @"Edit Permission";
         }
         
-        vc.user = user;
+        vc.permissionUser = user;
         vc.permissions = permissions;
         vc.account = account;
         vc.container = container;
     } 
 }
 
-#pragma mark - Helper functions
-
-- (void)reloadMetadataSection {
-    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading metadata..."
-                                                                                                   andAddToView:self.view];
-    [[self.account.manager getObjectInfo:container object:object version:nil]
-     success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
-         folder.metadata = [NSMutableDictionary dictionary];
-         for (NSString *header in request.responseHeaders) {
-             NSString *metadataKey;
-             NSString *metadataValue;
-             if ([header rangeOfString:@"X-Object-Meta-"].location != NSNotFound) {
-                 metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:14]];
-                 metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
-                 [folder.metadata setObject:metadataValue forKey:metadataKey];
-             }
-         }   
-         object.metadata = folder.metadata;
-         NSIndexSet *metadataSections = [NSIndexSet indexSetWithIndex:kMetadata];
-         [self.tableView reloadSections:metadataSections withRowAnimation:UITableViewRowAnimationFade];
-     }
-     failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
-         [self alert:@"There was a problem retrieving the object's metadata." request:request]; 
-     }];
-}
-
 @end
index 65d420a..817aa27 100755 (executable)
@@ -9,44 +9,36 @@
 #import "OpenStackViewController.h"
 #import "Folder.h"
 
-@class OpenStackAccount, Container, StorageObject, ContainersViewController, FolderDetailViewController, StorageObjectViewController;
+@class OpenStackAccount, Container, StorageObject, Folder, ContainersViewController, FolderDetailViewController, StorageObjectViewController;
 
 @interface FolderViewController : OpenStackViewController <UITableViewDelegate, UITableViewDataSource, UISearchDisplayDelegate, UIActionSheetDelegate, UIDocumentInteractionControllerDelegate> {
     OpenStackAccount *account;
     Container *container;
     Folder *folder;
     NSString *name;
-    id successObserver;
-    id failureObserver;
-    
     ContainersViewController *containersViewController;
     FolderViewController *parentFolderViewController;
-    NSIndexPath *selectedContainerIndexPath;
-    NSIndexPath *selectedFolderIndexPath;
-    BOOL contentsLoaded;
-    BOOL needsRefreshing;
-    BOOL folderHasBeenRemoved;
     BOOL refreshWhenAppeared;
-    BOOL searchActiveOnRotation;
+    id deletedObject;
     
-    IBOutlet UITableView *tableView;
-    IBOutlet UIBarButtonItem *homeButton;
-    IBOutlet UIBarButtonItem *refreshButton;
-    IBOutlet UISearchBar *searchBar;
-    IBOutlet UISearchDisplayController *searchDisplayController;
+    UISearchDisplayController *searchDisplayController;
+    UITableView *tableView;
+    UISearchBar *searchBar;
+    UIBarButtonItem *refreshButton;
+    UIBarButtonItem *sortTypeButton;
+    UIBarButtonItem *sortDirectionButton;
     
     NSString *searchFilter;
-    
-    IBOutlet UIBarButtonItem *sortTypeButton;
-    IBOutlet UIBarButtonItem *sortDirectionButton;
-
+    BOOL searchActiveOnRotation;
     FolderSortType sortType;
     FolderSortDirection sortNameDirection;
     FolderSortDirection sortDateDirection;
+    
+    id successObserver;
+    id failureObserver;
 
     FolderDetailViewController *folderDetailVC;
     StorageObjectViewController *selectedObjectViewController;
-    
     UIDocumentInteractionController *documentInteractionController;
 }
 
 @property (nonatomic, retain) Folder *folder;
 @property (nonatomic, retain) NSString *name;
 @property (nonatomic, retain) ContainersViewController *containersViewController;
-@property (nonatomic, retain) NSIndexPath *selectedContainerIndexPath;
 @property (nonatomic, retain) FolderViewController *parentFolderViewController;
-@property (nonatomic, retain) NSIndexPath *selectedFolderIndexPath;
-@property (nonatomic, assign) BOOL contentsLoaded;
-@property (nonatomic, assign) BOOL needsRefreshing;
-@property (nonatomic, assign) BOOL folderHasBeenRemoved;
 @property (nonatomic, assign) BOOL refreshWhenAppeared;
-@property (nonatomic, assign) BOOL searchActiveOnRotation;
+@property (nonatomic, retain) id deletedObject;
+
+@property (nonatomic, retain) IBOutlet UISearchDisplayController *searchDisplayController;
 @property (nonatomic, retain) IBOutlet UITableView *tableView;
-@property (nonatomic, retain) IBOutlet UIBarButtonItem *refreshButton;
 @property (nonatomic, retain) IBOutlet UISearchBar *searchBar;
-@property (nonatomic, retain) IBOutlet UISearchDisplayController *searchDisplayController;
-@property (nonatomic, retain) NSString *searchFilter;
+@property (nonatomic, retain) IBOutlet UIBarButtonItem *refreshButton;
 @property (nonatomic, retain) IBOutlet UIBarButtonItem *sortTypeButton;
 @property (nonatomic, retain) IBOutlet UIBarButtonItem *sortDirectionButton;
+
+@property (nonatomic, retain) NSString *searchFilter;
+@property (nonatomic, assign) BOOL searchActiveOnRotation;
 @property (nonatomic, assign) FolderSortType sortType;
 @property (nonatomic, assign) FolderSortDirection sortNameDirection;
 @property (nonatomic, assign) FolderSortDirection sortDateDirection;
+
 @property (nonatomic, assign) FolderDetailViewController *folderDetailVC;
 @property (nonatomic, assign) StorageObjectViewController *selectedObjectViewController;
 @property (nonatomic, retain) UIDocumentInteractionController *documentInteractionController;
 
-- (IBAction)homeButtonPressed:(id)sender;
 - (IBAction)refreshButtonPressed:(id)sender;
 - (IBAction)toggleSortType:(id)sender;
 - (IBAction)toggleSortDirection:(id)sender;
+- (IBAction)homeButtonPressed:(id)sender;
 
 - (void)reloadData;
 - (void)setDetailViewController;
-- (void)deleteAnimatedObject:(StorageObject *)object;
+- (void)deleteAnimatedObject:(id)object;
 
 @end
index 8317cce..51adf6a 100755 (executable)
@@ -35,10 +35,10 @@ static NSString *kSortDateDirectionKey = @"sortDateDirection";
 
 @implementation FolderViewController
 
-@synthesize account, container, folder, name, containersViewController, selectedContainerIndexPath, contentsLoaded, parentFolderViewController, selectedFolderIndexPath, tableView, needsRefreshing, folderHasBeenRemoved, refreshWhenAppeared, folderDetailVC, selectedObjectViewController, refreshButton;
-@synthesize searchBar, searchDisplayController, searchFilter, searchActiveOnRotation;
-@synthesize sortTypeButton, sortDirectionButton, sortType, sortNameDirection, sortDateDirection;
-@synthesize documentInteractionController;
+@synthesize account, container, folder, name, containersViewController, parentFolderViewController, refreshWhenAppeared, deletedObject;
+@synthesize searchDisplayController, tableView ,searchBar, refreshButton, sortTypeButton, sortDirectionButton;
+@synthesize searchFilter, searchActiveOnRotation, sortType, sortNameDirection, sortDateDirection;;
+@synthesize folderDetailVC, selectedObjectViewController, documentInteractionController;
 
 #pragma mark - View lifecycle
 
@@ -76,16 +76,12 @@ static NSString *kSortDateDirectionKey = @"sortDateDirection";
 - (void)viewDidLoad {
     [super viewDidLoad];
     [self addAddButton];
-    [self addHomeButton];
 }
 
 - (void)viewWillAppear:(BOOL)animated {
     [super viewWillAppear:animated];
-    if (self.name && self.name.length) {
-        self.navigationItem.title = self.name;
-    } else {
-        self.navigationItem.title = self.container.name;
-    }
+
+    self.navigationItem.title = (self.name.length ? self.name : self.container.name);
     
     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
     
@@ -99,37 +95,61 @@ static NSString *kSortDateDirectionKey = @"sortDateDirection";
     } else {
         self.sortDirectionButton.title = (self.sortNameDirection == FolderSortNameDirectionZA) ? @"Z-A" : @"A-Z";
     }
-    
-    [self reloadData];
+
+    if (!container.rootFolder || !deletedObject) {
+        self.deletedObject = nil;
+        [self reloadData];
+    } else if ([[deletedObject class] isEqual:[Folder class]]) {
+        Folder *deletedFolder = (Folder *)deletedObject;
+        if (![folder.folders objectForKey:deletedFolder.name] ||
+            ([[self searchResults] indexOfObject:[folder.folders objectForKey:deletedFolder.name]] == NSNotFound)) {
+            self.deletedObject = nil;
+            [self reloadData];
+        } else if (folder.objectsAndFoldersCount == 1) {
+            [self deleteAnimatedObject:deletedObject];
+        }
+    } else if ([[deletedObject class] isEqual:[StorageObject class]]) {
+        StorageObject *deletedStorageObject = (StorageObject *)deletedObject;
+        if (![folder.objects objectForKey:deletedStorageObject.name] ||
+            ([[self searchResults] indexOfObject:[folder.objects objectForKey:deletedStorageObject.name]] == NSNotFound)) {
+            self.deletedObject = nil;
+            [self reloadData];
+        } else if (folder.objectsAndFoldersCount == 1) {
+            [self deleteAnimatedObject:deletedObject];
+        }
+    } else {
+        self.deletedObject = nil;
+    }
 }
 
 - (void)viewDidAppear:(BOOL)animated {
     [super viewDidAppear:animated];
-    if (!self.folder) {
-        if (folderHasBeenRemoved) {
-            if (needsRefreshing) 
-                self.parentFolderViewController.needsRefreshing = YES;
-            [self.navigationController popViewControllerAnimated:YES];
-        } else {
-            [self refreshButtonPressed:nil];
-        }
-    } else if (self.needsRefreshing) {
-        self.parentFolderViewController.needsRefreshing = YES;
-        if (refreshWhenAppeared) {
-            [self refreshButtonPressed:nil];
-            refreshWhenAppeared = NO;
-        }
-        if (account.shared && !folder.objectsAndFoldersCount) {
-            [self.navigationController popViewControllerAnimated:YES];
-        }
+    
+    if (!container.rootFolder) {
+        // Container objects not fetched yet.
+        refreshWhenAppeared = YES;
+    } else if (!folder) {
+        // This controller's folder has been deleted.
+        parentFolderViewController.refreshWhenAppeared = YES;
+        [self.navigationController popViewControllerAnimated:YES];
+        return;
+    } else if (account.shared && !folder.objectsAndFoldersCount) {
+        // The shared folder is empty, probably after a delete.
+        [self.navigationController popViewControllerAnimated:YES];
+        return;
+    } else if (deletedObject) {
+        [self deleteAnimatedObject:deletedObject];
+    }
+    
+    if (refreshWhenAppeared) {
+        refreshWhenAppeared = NO;
+        [self refreshButtonPressed:nil];
     }
 }
 
 - (void)viewWillDisappear:(BOOL)animated {
     [super viewWillDisappear:animated];
-    if (![self.navigationController.viewControllers containsObject:self] &&
-        self.parentViewController &&
-        ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)) {
+    if (![self.navigationController.viewControllers containsObject:self] && self.parentViewController) {
         [parentFolderViewController setDetailViewController];
     }
 }
@@ -140,16 +160,16 @@ static NSString *kSortDateDirectionKey = @"sortDateDirection";
     [account release];
     [container release];
     [folder release];
+    [name release];
     [containersViewController release];
-    [selectedContainerIndexPath release];
     [parentFolderViewController release];
-    [selectedFolderIndexPath release];
-    [refreshButton release];
-    [searchBar release];
     [searchDisplayController release];
-    [searchFilter release];
+    [searchBar release];
+    [refreshButton release];
     [sortTypeButton release];
     [sortDirectionButton release];
+    [deletedObject release];
+    [searchFilter release];
     [documentInteractionController release];
     [super dealloc];
 }
@@ -193,37 +213,27 @@ static NSString *kSortDateDirectionKey = @"sortDateDirection";
         Folder *parentFolder = folderViewController.parentFolderViewController.folder;
         
         folderViewController.folder = [parentFolder.folders objectForKey:folderViewController.folder.name];
-        if (!folderViewController.folder)
-            folderViewController.folderHasBeenRemoved = YES;
     }
     if (!self.folder || (account.shared && !folder.objectsAndFoldersCount)) {
-        if (needsRefreshing && self.parentFolderViewController) {
-            self.parentFolderViewController.needsRefreshing = YES;
-        }
         [self.navigationController popViewControllerAnimated:YES];
-    } else if (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) && account.shared) {
+    } else if (account.shared) {
         [self setDetailViewController];
     }
 }
 
-- (void)deleteFolderRow {
-    [self.parentFolderViewController.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedFolderIndexPath]
-                                                     withRowAnimation:UITableViewRowAnimationLeft];
-    if (self.parentFolderViewController.searchDisplayController.active &&
-        self.parentFolderViewController.searchFilter && self.parentFolderViewController.searchFilter.length)
-        [self.parentFolderViewController.searchDisplayController.searchResultsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedFolderIndexPath]
-                                                                                              withRowAnimation:UITableViewRowAnimationLeft];
-}
-
-- (void)deleteObjectRow:(NSTimer *)timer {
-    [self.folder removeObject:[timer.userInfo objectForKey:@"object"]];
-    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[timer.userInfo objectForKey:@"indexPath"]]
-                          withRowAnimation:UITableViewRowAnimationLeft];
-    if (self.searchDisplayController.active && self.searchFilter && self.searchFilter.length)
-        [self.searchDisplayController.searchResultsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[timer.userInfo objectForKey:@"indexPath"]]
-                                                                   withRowAnimation:UITableViewRowAnimationLeft];
-    self.refreshButton.enabled = YES;
-}
+//- (void)deleteObjectRow:(NSTimer *)timer {
+//    [self.folder removeObject:[timer.userInfo objectForKey:@"object"]];
+//    if (folder == container.rootFolder) {
+//        container.count -= 1;
+//    }
+//    [account persist];
+//    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[timer.userInfo objectForKey:@"indexPath"]]
+//                          withRowAnimation:UITableViewRowAnimationLeft];
+//    if (self.searchDisplayController.active && self.searchFilter && self.searchFilter.length)
+//        [self.searchDisplayController.searchResultsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:[timer.userInfo objectForKey:@"indexPath"]]
+//                                                                   withRowAnimation:UITableViewRowAnimationLeft];
+//    self.refreshButton.enabled = YES;
+//}
 
 - (NSArray *)searchResults {
 return [self.folder sortedContentsUsingFilter:self.searchFilter
@@ -232,12 +242,18 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
                                 sortDirection:((self.sortType == FolderSortTypeDate) ? self.sortDateDirection : self.sortNameDirection)];
 }
 
+- (void)delete {
+    parentFolderViewController.deletedObject = folder;
+    [self.navigationController popViewControllerAnimated:YES];
+    [parentFolderViewController setDetailViewController];
+}
+
 #pragma mark - Properties
 
 - (void)setFolder:(Folder *)aFolder {
     [folder release];
     folder = [aFolder retain];
-    [self showSearchBar:(self.contentsLoaded && self.folder && self.folder.objectsAndFoldersCount)];
+    [self showSearchBar:(self.container.rootFolder && self.folder && self.folder.objectsAndFoldersCount)];
     if (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) && self.selectedObjectViewController && folder) {
         StorageObject *newObject = [folder.objects objectForKey:self.selectedObjectViewController.object.name];
         if (newObject)
@@ -270,38 +286,96 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
 }
 
 - (void)setDetailViewController {
-    self.selectedObjectViewController = nil;
-    if ([self.folder isEqual:self.container.rootFolder]) {
-        ContainerDetailViewController *vc = [[ContainerDetailViewController alloc] initWithNibName:@"ContainerDetailViewController" bundle:nil];
-        vc.account = self.account;
-        vc.container = self.container;
-        vc.containersViewController = self.containersViewController;
-        vc.rootFolderViewController = self;
-        vc.selectedContainerIndexPath = self.selectedContainerIndexPath;
-        [self presentPrimaryViewController:vc];
-        self.containersViewController.containerDetailViewController = vc;
-        [vc release];
-    } else {
-        FolderDetailViewController *vc = [[FolderDetailViewController alloc] initWithNibName:@"FolderDetailViewController" bundle:nil];
-        vc.account = self.account;
-        vc.container = self.container;
-        vc.folder = self.folder;
-        vc.folderViewController = self.parentFolderViewController;
-        [self presentPrimaryViewController:vc];
-        self.parentFolderViewController.folderDetailVC = vc;
-        [vc release];
+    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
+        self.selectedObjectViewController = nil;
+        if (self.folder) {
+            if ([self.folder isEqual:self.container.rootFolder]) {
+                [containersViewController setDetailViewControllerForContainer:container];
+            } else {
+                FolderDetailViewController *vc = [[FolderDetailViewController alloc] initWithNibName:@"FolderDetailViewController" bundle:nil];
+                vc.account = self.account;
+                vc.container = self.container;
+                vc.folder = self.folder;
+                vc.folderViewController = self.parentFolderViewController;
+                [self presentPrimaryViewController:vc];
+                self.parentFolderViewController.folderDetailVC = vc;
+                [vc release];
+                containersViewController.containerDetailViewController = nil;
+            }
+        }
     }
 }
 
-- (void)deleteAnimatedObject:(StorageObject *)object {
-    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:[[self searchResults] indexOfObject:object] inSection:0];
-    [self.tableView selectRowAtIndexPath:deleteIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
-    if (self.searchDisplayController.active && self.searchFilter && self.searchFilter.length)
-        [self.searchDisplayController.searchResultsTableView selectRowAtIndexPath:deleteIndexPath
-                                                                         animated:YES
-                                                                   scrollPosition:UITableViewScrollPositionNone];
-    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:object, @"object", deleteIndexPath, @"indexPath", nil];
-    [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(deleteObjectRow:) userInfo:userInfo repeats:NO];
+//- (void)deleteAnimatedObject:(StorageObject *)object {
+//    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:[[self searchResults] indexOfObject:object] inSection:0];
+//    [self.tableView selectRowAtIndexPath:deleteIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
+//    if (self.searchDisplayController.active && self.searchFilter && self.searchFilter.length)
+//        [self.searchDisplayController.searchResultsTableView selectRowAtIndexPath:deleteIndexPath
+//                                                                         animated:YES
+//                                                                   scrollPosition:UITableViewScrollPositionNone];
+//    NSDictionary *userInfo = [NSDictionary dictionaryWithObjectsAndKeys:object, @"object", deleteIndexPath, @"indexPath", nil];
+//    [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(deleteObjectRow:) userInfo:userInfo repeats:NO];
+//}
+
+- (void)deleteAnimatedObject:(id)object {
+    NSIndexPath *deletedObjectIndexPath = nil;
+    if ([[object class] isEqual:[Folder class]]) {
+        Folder *deletedFolder = [folder.folders objectForKey:[object name]];
+        if (deletedFolder) {
+            if (folder.objectsAndFoldersCount == 1) {
+                // If the deleted folder is the sole object of the folder, remove and reload to show the empty folder cell.
+                [folder removeFolder:deletedFolder];
+                if (folder == container.rootFolder) {
+                    container.count -= 1;
+                }
+                [account persist];
+                self.folder = self.folder;
+            } else {
+                NSUInteger deletedFolderIndex = [[self searchResults] indexOfObject:deletedFolder];
+                if (deletedFolderIndex != NSNotFound) {
+                    deletedObjectIndexPath = [NSIndexPath indexPathForRow:deletedFolderIndex inSection:0];
+                }
+                [folder removeFolder:deletedFolder];
+                if (folder == container.rootFolder) {
+                    container.count -= 1;
+                }
+                [account persist];
+            }
+        }
+    } else if ([[object class] isEqual:[StorageObject class]]) {
+        StorageObject *deletedStorageObject = [folder.objects objectForKey:[object name]];
+        if (deletedStorageObject) {
+            if (folder.objectsAndFoldersCount == 1) {
+                // If the deleted storage object is the sole object of the folder, remove and reload to show the empty folder cell.
+                [folder removeObject:deletedStorageObject];
+                if (folder == container.rootFolder) {
+                    container.count -= 1;
+                }
+                [account persist];
+                self.folder = self.folder;
+            } else {
+                NSUInteger deletedStorageObjectIndex = [[self searchResults] indexOfObject:deletedStorageObject];
+                if (deletedStorageObjectIndex != NSNotFound) {
+                    deletedObjectIndexPath = [NSIndexPath indexPathForRow:deletedStorageObjectIndex inSection:0];
+                }
+                [folder removeObject:deletedStorageObject];
+                if (folder == container.rootFolder) {
+                    container.count -= 1;
+                }
+                [account persist];
+            }
+        }
+    }
+    if (deletedObjectIndexPath) {
+        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:deletedObjectIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
+        if (searchDisplayController.active && searchFilter && searchFilter.length) {
+            [searchDisplayController.searchResultsTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:deletedObjectIndexPath]
+                                                                  withRowAnimation:UITableViewRowAnimationLeft];
+        }
+    }
+    self.deletedObject = nil;
+    refreshWhenAppeared = NO;
+    [self refreshButtonPressed:nil];
 }
 
 - (IBAction)toggleSortType:(id)sender {
@@ -351,7 +425,7 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
     if (self.folder.objectsAndFoldersCount) {
         // For the search results tableView this will be returned because search is allowed only when the folder is not empty.
         return [[self searchResults] count];
-    } else if (contentsLoaded) {
+    } else if (self.container.rootFolder) {
         return 1;
     } else {
         return 0;
@@ -492,11 +566,9 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
             vc.account = self.account;
             vc.container = self.container;
             vc.name = [item name];
-            vc.selectedFolderIndexPath = indexPath;
             vc.parentFolderViewController = self;
             vc.containersViewController = self.containersViewController;
             [self.navigationController pushViewController:vc animated:YES];
-            vc.contentsLoaded = YES;
             vc.folder = item;
             [aTableView deselectRowAtIndexPath:indexPath animated:YES];
             if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
@@ -655,11 +727,24 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
 
 #pragma mark - Button Handlers
 
-- (IBAction)homeButtonPressed:(id)sender {
-    [self.navigationController popToViewController:containersViewController.accountHomeViewController animated:YES];
+- (void)addButtonPressed:(id)sender {
+    AddObjectViewController *vc = [[AddObjectViewController alloc] initWithNibName:@"AddObjectViewController" bundle:nil];
+    vc.account = self.account;
+    vc.container = self.container;
+    vc.folder = self.folder;
+    vc.folderViewController = self;
+    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
+        vc.modalPresentationStyle = UIModalPresentationFormSheet;
+        OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
+        if (app.rootViewController.popoverController != nil) {
+            [app.rootViewController.popoverController dismissPopoverAnimated:YES];
+        }
+    }
+    [self presentModalViewControllerWithNavigation:vc animated:YES];
+    [vc release];
 }
 
-- (IBAction)refreshButtonPressed:(id)sender {
+- (void)refreshButtonPressed:(id)sender {
     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading..."
                                                                                                    andAddToView:self.view];
     [self.account.manager getObjects:self.container];
@@ -668,10 +753,8 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
                                                                          queue:[NSOperationQueue mainQueue]
                                                                     usingBlock:^(NSNotification* notification)
     {
-        [activityIndicatorView removeFromSuperview];
-        contentsLoaded = YES;
+        [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
         [self reloadFolderViewControllers];
-        needsRefreshing = NO;
         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
            if (self.selectedObjectViewController) {
                [self.selectedObjectViewController reloadMetadataSection];
@@ -690,8 +773,7 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
                                                                          queue:[NSOperationQueue mainQueue]
                                                                     usingBlock:^(NSNotification* notification)
     {
-        [activityIndicatorView removeFromSuperview];
-        needsRefreshing = NO;
+        [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
         [self reloadData];
         [self alert:@"Error" message:@"Failed to retrieve files from server."];
         [[NSNotificationCenter defaultCenter] removeObserver:successObserver];
@@ -699,21 +781,8 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
     }];
 }
 
-- (void)addButtonPressed:(id)sender {
-    AddObjectViewController *vc = [[AddObjectViewController alloc] initWithNibName:@"AddObjectViewController" bundle:nil];
-    vc.account = self.account;
-    vc.container = self.container;
-    vc.folder = self.folder;
-    vc.folderViewController = self;
-    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-        vc.modalPresentationStyle = UIModalPresentationFormSheet;
-        OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
-        if (app.rootViewController.popoverController != nil) {
-            [app.rootViewController.popoverController dismissPopoverAnimated:YES];
-        }
-    }
-    [self presentModalViewControllerWithNavigation:vc animated:YES];
-    [vc release];
+- (void)homeButtonPressed:(id)sender {
+    [self.navigationController popToViewController:containersViewController.accountHomeViewController animated:YES];
 }
 
 - (void)deleteButtonPressed:(id)sender {
@@ -734,47 +803,24 @@ return [self.folder sortedContentsUsingFilter:self.searchFilter
 
 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
     if (buttonIndex == 0) {
-        StorageObject *object = [[StorageObject alloc] init];
+        StorageObject *object = [[[StorageObject alloc] init] autorelease];
         object.name = object.fullPath = [self.folder fullPath];
-        
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Deleting folder..."
                                                                                                        andAddToView:self.view
                                                                                                        scrollOffset:self.tableView.contentOffset.y];
         [[self.account.manager deleteObject:self.container object:object]
          success:^(OpenStackRequest *request) {
-             if (self.folder.parent) {
-                 [self.folder.parent removeFolder:self.folder];
-             } else {
-                 [self.container.rootFolder removeFolder:self.folder];
-             }
-             [self.account persist];
-             
-             [activityIndicatorView removeFromSuperview];
-             [self.navigationController popViewControllerAnimated:YES];
-             
-             if ((self.folder.parent && !self.folder.parent.objectsAndFoldersCount) ||
-                 (!self.folder.parent && !self.container.rootFolder.objectsAndFoldersCount)) {
-                 self.parentFolderViewController.folder = self.parentFolderViewController.folder;
-             } else if ((self.folder.parent && self.folder.parent.objectsAndFoldersCount) ||
-                        (!self.folder.parent && self.container.rootFolder.objectsAndFoldersCount)) {
-                 [self.parentFolderViewController.tableView selectRowAtIndexPath:selectedFolderIndexPath
-                                                                        animated:YES
-                                                                  scrollPosition:UITableViewScrollPositionNone];
-                 if (self.parentFolderViewController.searchDisplayController.active &&
-                     self.parentFolderViewController.searchFilter && self.parentFolderViewController.searchFilter.length)
-                     [self.parentFolderViewController.searchDisplayController.searchResultsTableView selectRowAtIndexPath:selectedFolderIndexPath
-                                                                                                                 animated:YES
-                                                                                                           scrollPosition:UITableViewScrollPositionNone];
-                 [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(deleteFolderRow) userInfo:nil repeats:NO];
-             }
-             if (([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) && self.folder.parent)
-                 [self.parentFolderViewController setDetailViewController];
-             [object release];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self delete];
          }
          failure:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             [self alert:@"There was a problem deleting this folder." request:request];
-             [object release];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             // 404 Not Found means it's not there, so we can show the user that it's deleted
+             if (request.responseStatusCode == 404) {
+                 [self delete];
+             } else {
+                 [self alert:@"There was a problem deleting this folder." request:request];
+             }
          }];
     }
 }
index 1e8b210..8217ea2 100755 (executable)
@@ -48,7 +48,7 @@
                                                <int key="NSvFlags">266</int>
                                                <string key="NSFrame">{{0, 416}, {320, 44}}</string>
                                                <reference key="NSSuperview" ref="228054471"/>
-                                               <reference key="NSNextKeyView"/>
+                                               <reference key="NSWindow"/>
                                                <bool key="IBUIOpaque">NO</bool>
                                                <bool key="IBUIClearsContextBeforeDrawing">NO</bool>
                                                <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
                                                                <reference key="IBUIToolbar" ref="599029925"/>
                                                                <int key="IBUISystemItemIdentifier">5</int>
                                                        </object>
+                                                       <object class="IBUIBarButtonItem" id="901170948">
+                                                               <object class="NSCustomResource" key="IBUIImage">
+                                                                       <string key="NSClassName">NSImage</string>
+                                                                       <string key="NSResourceName">HomeFolderIcon.png</string>
+                                                               </object>
+                                                               <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+                                                               <float key="IBUIWidth">33</float>
+                                                               <reference key="IBUIToolbar" ref="599029925"/>
+                                                       </object>
                                                </object>
                                        </object>
                                        <object class="IBUITableView" id="173989330">
                                                <int key="NSvFlags">274</int>
                                                <string key="NSFrameSize">{320, 416}</string>
                                                <reference key="NSSuperview" ref="228054471"/>
+                                               <reference key="NSWindow"/>
                                                <reference key="NSNextKeyView" ref="599029925"/>
                                                <object class="NSColor" key="IBUIBackgroundColor">
                                                        <int key="NSColorSpace">3</int>
                                </object>
                                <string key="NSFrameSize">{320, 460}</string>
                                <reference key="NSSuperview"/>
+                               <reference key="NSWindow"/>
                                <reference key="NSNextKeyView" ref="173989330"/>
                                <object class="NSColor" key="IBUIBackgroundColor">
                                        <int key="NSColorSpace">3</int>
                                <int key="NSvFlags">290</int>
                                <string key="NSFrameSize">{320, 44}</string>
                                <reference key="NSSuperview"/>
-                               <reference key="NSNextKeyView"/>
+                               <reference key="NSWindow"/>
                                <string key="NSReuseIdentifierKey">_NS:9</string>
                                <int key="IBUIContentMode">3</int>
                                <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
                                        </object>
                                        <int key="connectionID">63</int>
                                </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBCocoaTouchEventConnection" key="connection">
+                                               <string key="label">homeButtonPressed:</string>
+                                               <reference key="source" ref="901170948"/>
+                                               <reference key="destination" ref="372490531"/>
+                                       </object>
+                                       <int key="connectionID">66</int>
+                               </object>
                        </object>
                        <object class="IBMutableOrderedSet" key="objectRecords">
                                <object class="NSArray" key="orderedObjects">
                                                        <reference ref="190609300"/>
                                                        <reference ref="438761365"/>
                                                        <reference ref="970966376"/>
+                                                       <reference ref="901170948"/>
                                                </object>
                                                <reference key="parent" ref="228054471"/>
                                        </object>
                                                <reference key="object" ref="970966376"/>
                                                <reference key="parent" ref="599029925"/>
                                        </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">65</int>
+                                               <reference key="object" ref="901170948"/>
+                                               <reference key="parent" ref="599029925"/>
+                                       </object>
                                </object>
                        </object>
                        <object class="NSMutableDictionary" key="flattenedProperties">
                                        <string>49.IBPluginDependency</string>
                                        <string>58.IBPluginDependency</string>
                                        <string>59.IBPluginDependency</string>
+                                       <string>65.IBPluginDependency</string>
                                        <string>9.IBPluginDependency</string>
                                </object>
                                <object class="NSArray" key="dict.values">
                                        <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                        <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                        <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+                                       <string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                </object>
                        </object>
                        <object class="NSMutableDictionary" key="unlocalizedProperties">
                                <reference key="dict.values" ref="0"/>
                        </object>
                        <nil key="sourceID"/>
-                       <int key="maxID">64</int>
+                       <int key="maxID">66</int>
+               </object>
+               <object class="IBClassDescriber" key="IBDocument.Classes">
+                       <object class="NSMutableArray" key="referencedPartialClassDescriptions">
+                               <bool key="EncodedWithXMLCoder">YES</bool>
+                               <object class="IBPartialClassDescription">
+                                       <string key="className">FolderViewController</string>
+                                       <string key="superclassName">OpenStackViewController</string>
+                                       <object class="NSMutableDictionary" key="actions">
+                                               <bool key="EncodedWithXMLCoder">YES</bool>
+                                               <object class="NSArray" key="dict.sortedKeys">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>homeButtonPressed:</string>
+                                                       <string>refreshButtonPressed:</string>
+                                                       <string>toggleSortDirection:</string>
+                                                       <string>toggleSortType:</string>
+                                               </object>
+                                               <object class="NSArray" key="dict.values">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>id</string>
+                                                       <string>id</string>
+                                                       <string>id</string>
+                                                       <string>id</string>
+                                               </object>
+                                       </object>
+                                       <object class="NSMutableDictionary" key="actionInfosByName">
+                                               <bool key="EncodedWithXMLCoder">YES</bool>
+                                               <object class="NSArray" key="dict.sortedKeys">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>homeButtonPressed:</string>
+                                                       <string>refreshButtonPressed:</string>
+                                                       <string>toggleSortDirection:</string>
+                                                       <string>toggleSortType:</string>
+                                               </object>
+                                               <object class="NSArray" key="dict.values">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <object class="IBActionInfo">
+                                                               <string key="name">homeButtonPressed:</string>
+                                                               <string key="candidateClassName">id</string>
+                                                       </object>
+                                                       <object class="IBActionInfo">
+                                                               <string key="name">refreshButtonPressed:</string>
+                                                               <string key="candidateClassName">id</string>
+                                                       </object>
+                                                       <object class="IBActionInfo">
+                                                               <string key="name">toggleSortDirection:</string>
+                                                               <string key="candidateClassName">id</string>
+                                                       </object>
+                                                       <object class="IBActionInfo">
+                                                               <string key="name">toggleSortType:</string>
+                                                               <string key="candidateClassName">id</string>
+                                                       </object>
+                                               </object>
+                                       </object>
+                                       <object class="NSMutableDictionary" key="outlets">
+                                               <bool key="EncodedWithXMLCoder">YES</bool>
+                                               <object class="NSArray" key="dict.sortedKeys">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>refreshButton</string>
+                                                       <string>searchBar</string>
+                                                       <string>searchDisplayController</string>
+                                                       <string>sortDirectionButton</string>
+                                                       <string>sortTypeButton</string>
+                                                       <string>tableView</string>
+                                               </object>
+                                               <object class="NSArray" key="dict.values">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>UIBarButtonItem</string>
+                                                       <string>UISearchBar</string>
+                                                       <string>UISearchDisplayController</string>
+                                                       <string>UIBarButtonItem</string>
+                                                       <string>UIBarButtonItem</string>
+                                                       <string>UITableView</string>
+                                               </object>
+                                       </object>
+                                       <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+                                               <bool key="EncodedWithXMLCoder">YES</bool>
+                                               <object class="NSArray" key="dict.sortedKeys">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <string>refreshButton</string>
+                                                       <string>searchBar</string>
+                                                       <string>searchDisplayController</string>
+                                                       <string>sortDirectionButton</string>
+                                                       <string>sortTypeButton</string>
+                                                       <string>tableView</string>
+                                               </object>
+                                               <object class="NSArray" key="dict.values">
+                                                       <bool key="EncodedWithXMLCoder">YES</bool>
+                                                       <object class="IBToOneOutletInfo">
+                                                               <string key="name">refreshButton</string>
+                                                               <string key="candidateClassName">UIBarButtonItem</string>
+                                                       </object>
+                                                       <object class="IBToOneOutletInfo">
+                                                               <string key="name">searchBar</string>
+                                                               <string key="candidateClassName">UISearchBar</string>
+                                                       </object>
+                                                       <object class="IBToOneOutletInfo">
+                                                               <string key="name">searchDisplayController</string>
+                                                               <string key="candidateClassName">UISearchDisplayController</string>
+                                                       </object>
+                                                       <object class="IBToOneOutletInfo">
+                                                               <string key="name">sortDirectionButton</string>
+                                                               <string key="candidateClassName">UIBarButtonItem</string>
+                                                       </object>
+                                                       <object class="IBToOneOutletInfo">
+                                                               <string key="name">sortTypeButton</string>
+                                                               <string key="candidateClassName">UIBarButtonItem</string>
+                                                       </object>
+                                                       <object class="IBToOneOutletInfo">
+                                                               <string key="name">tableView</string>
+                                                               <string key="candidateClassName">UITableView</string>
+                                                       </object>
+                                               </object>
+                                       </object>
+                                       <object class="IBClassDescriptionSource" key="sourceIdentifier">
+                                               <string key="majorKey">IBProjectSource</string>
+                                               <string key="minorKey">./Classes/FolderViewController.h</string>
+                                       </object>
+                               </object>
+                               <object class="IBPartialClassDescription">
+                                       <string key="className">OpenStackViewController</string>
+                                       <string key="superclassName">UIViewController</string>
+                                       <object class="NSMutableDictionary" key="outlets">
+                                               <string key="NS.key.0">toolbar</string>
+                                               <string key="NS.object.0">UIToolbar</string>
+                                       </object>
+                                       <object class="NSMutableDictionary" key="toOneOutletInfosByName">
+                                               <string key="NS.key.0">toolbar</string>
+                                               <object class="IBToOneOutletInfo" key="NS.object.0">
+                                                       <string key="name">toolbar</string>
+                                                       <string key="candidateClassName">UIToolbar</string>
+                                               </object>
+                                       </object>
+                                       <object class="IBClassDescriptionSource" key="sourceIdentifier">
+                                               <string key="majorKey">IBProjectSource</string>
+                                               <string key="minorKey">./Classes/OpenStackViewController.h</string>
+                                       </object>
+                               </object>
+                       </object>
                </object>
-               <object class="IBClassDescriber" key="IBDocument.Classes"/>
                <int key="IBDocument.localizationMode">0</int>
                <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
                <object class="NSMutableDictionary" key="IBDocument.PluginDeclaredDevelopmentDependencies">
                </object>
                <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
                <int key="IBDocument.defaultPropertyAccessControl">3</int>
+               <object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
+                       <string key="NS.key.0">HomeFolderIcon.png</string>
+                       <string key="NS.object.0">{22, 22}</string>
+               </object>
                <string key="IBCocoaTouchPluginVersion">1929</string>
        </data>
 </archive>
diff --git a/Classes/GetContainersRequest.h b/Classes/GetContainersRequest.h
deleted file mode 100755 (executable)
index e274d52..0000000
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-//  GetContainersRequest.h
-//  OpenStack
-//
-//  Created by Mike Mayo on 12/24/10.
-//  The OpenStack project is provided under the Apache 2.0 license.
-//
-
-#import "OpenStackRequest.h"
-
-@class OpenStackAccount;
-
-@interface GetContainersRequest : OpenStackRequest {
-}
-
-+ (GetContainersRequest *)request:(OpenStackAccount *)account;
-
-@end
diff --git a/Classes/GetContainersRequest.m b/Classes/GetContainersRequest.m
deleted file mode 100755 (executable)
index 8cdc259..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-//
-//  GetContainersRequest.m
-//  OpenStack
-//
-//  Created by Mike Mayo on 12/24/10.
-//  The OpenStack project is provided under the Apache 2.0 license.
-//
-
-#import "GetContainersRequest.h"
-#import "OpenStackAccount.h"
-#import "Container.h"
-#import "AccountManager.h"
-
-@implementation GetContainersRequest
-
-+ (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
-       GetContainersRequest *request = [[[GetContainersRequest alloc] initWithURL:url] autorelease];
-    request.account = account;
-       [request setRequestMethod:method];
-       [request addRequestHeader:@"X-Auth-Token" value:[account authToken]];
-    [request addRequestHeader:@"Content-Type" value:@"application/json"];
-       return request;
-}
-
-+ (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
-       NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@",
-                                       account.filesURL,
-                                       path,
-                                       account.shared ? @"&shared=" : @""]];
-    
-    return [GetContainersRequest request:account method:method url:url];
-}
-
-+ (GetContainersRequest *)request:(OpenStackAccount *)account {
-    GetContainersRequest *request = [GetContainersRequest filesRequest:account method:@"GET" path:@""];
-    request.account = account;
-    [request setTimeOutSeconds:60];
-    [request setNumberOfTimesToRetryOnTimeout:5];
-    return request;
-}
-
-- (void)requestFinished {
-    
-    if ([self isSuccess]) {
-        self.account.containers = [self containers];
-        self.account.containerCount = [self.account.containers count];
-        [self.account persist];
-        [self.account.manager notify:@"getContainersSucceeded" request:self object:self.account];
-    } else {
-        [self.account.manager notify:@"getContainersFailed" request:self object:self.account];
-    }
-    
-    [super requestFinished];
-}
-
-- (void)failWithError:(NSError *)theError {
-    [self.account.manager notify:@"getContainersFailed" request:self object:self.account];
-    [super failWithError:theError];
-}
-
-
-@end
index 941da9c..10a275e 100755 (executable)
 
 @property (nonatomic, retain) Container *container;
 
-+ (GetObjectsRequest *)request:(OpenStackAccount *)account container:(Container *)container;
-+ (GetObjectsRequest *)request:(OpenStackAccount *)account
-                     container:(Container *)container
-                        marker:(NSString *)marker
-                 objectsBuffer:(NSMutableDictionary *)objectsBuffer;
++ (id)request:(OpenStackAccount *)account container:(Container *)container;
++ (id)request:(OpenStackAccount *)account container:(Container *)container
+       marker:(NSString *)marker objectsBuffer:(NSMutableDictionary *)objectsBuffer;
 
 @end
index 4f16f93..585dbf3 100755 (executable)
 
 @synthesize container;
 
+#pragma mark - Constructors
+
 + (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
-       GetObjectsRequest *request = [[[GetObjectsRequest alloc] initWithURL:url] autorelease];
+       GetObjectsRequest *request = [[[self
+                                    alloc] initWithURL:url] autorelease];
     request.account = account;
-       [request setRequestMethod:method];
-       [request addRequestHeader:@"X-Auth-Token" value:[account authToken]];
+       request.requestMethod = method;
+       [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
     [request addRequestHeader:@"Content-Type" value:@"application/json"];
+    request.timeOutSeconds = 60;
+    request.numberOfTimesToRetryOnTimeout = 5;
        return request;
 }
 
 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path marker:(NSString *)marker {
-    NSString *filesUrl;
     NSString *urlString = [account.filesURL description];
     if (account.sharingAccount) {
-        NSRange authUserRange = [urlString rangeOfString:account.username];
-        filesUrl = [NSString stringWithFormat:@"%@%@",
-                    [urlString substringToIndex:authUserRange.location],
-                    account.sharingAccount];
-    } else
-        filesUrl = urlString;
-    
+        urlString = [NSString stringWithFormat:@"%@%@",
+                     [urlString substringToIndex:[urlString rangeOfString:account.username].location],
+                     account.sharingAccount];
+    }
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@%@",
-                                       filesUrl,
+                                       urlString,
                                        path,
-                                       account.shared ? @"&shared=" : @"",
-                                       marker ? [NSString stringWithFormat:@"&marker=%@", marker] : @""]];
-
-    return [GetObjectsRequest request:account method:method url:url];
+                                       (account.shared ? @"&shared=" : @""),
+                                       (marker ? [NSString stringWithFormat:@"&marker=%@", marker] : @"")]];
+    return [self request:account method:method url:url];
 }
 
 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
-    return [GetObjectsRequest filesRequest:account method:method path:path marker:nil];
+    return [self filesRequest:account method:method path:path marker:nil];
 }
 
-+ (GetObjectsRequest *)request:(OpenStackAccount *)account container:(Container *)container {
-    return [GetObjectsRequest request:account container:container marker:nil objectsBuffer:nil];
++ (id)request:(OpenStackAccount *)account container:(Container *)container {
+    return [self request:account container:container marker:nil objectsBuffer:nil];
 }
 
-+ (GetObjectsRequest *)request:(OpenStackAccount *)account
-                     container:(Container *)container
-                        marker:(NSString *)marker
-                 objectsBuffer:(NSMutableDictionary *)objectsBuffer
-{
-    GetObjectsRequest *request = [GetObjectsRequest filesRequest:account method:@"GET"
-                                                            path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]] 
-                                                          marker:[NSString encodeToPercentEscape:marker]];    
-    request.account = account;
++ (id)request:(OpenStackAccount *)account container:(Container *)container
+       marker:(NSString *)marker objectsBuffer:(NSMutableDictionary *)objectsBuffer {
+    GetObjectsRequest *request = [self filesRequest:account
+                                             method:@"GET"
+                                               path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]
+                                             marker:[NSString encodeToPercentEscape:marker]];
     request.container = container;
     if (objectsBuffer == nil)
         objectsBuffer = [NSMutableDictionary dictionary];
-    [request setUserInfo:[NSMutableDictionary dictionaryWithObject:objectsBuffer forKey:@"objectsBuffer"]];
-    [request setTimeOutSeconds:60];
-    [request setNumberOfTimesToRetryOnTimeout:5];
+    request.userInfo = [NSMutableDictionary dictionaryWithObject:objectsBuffer forKey:@"objectsBuffer"];
     return request;
 }
 
+#pragma mark - ASIHTTPRequest Overrides
 
 - (void)requestFinished {
     if ([self isSuccess]) {
-        Container *aContainer = self.container;  //[self.userInfo objectForKey:@"container"];
+        Container *aContainer = self.container;
         NSMutableDictionary *objects = [self objects];
         NSMutableDictionary *objectsBuffer = [self.userInfo objectForKey:@"objectsBuffer"];
-
-        SBJSON *parser = [[SBJSON alloc] init];
-        NSArray *jsonObjects = [parser objectWithString:[self responseString]];
-        NSUInteger numberOfObjectsInResponse = [jsonObjects count];
-        [parser release];
-        
         [objectsBuffer addEntriesFromDictionary:objects];
-        if (numberOfObjectsInResponse < 10000) {
+        if (objects.count < 10000) {
             aContainer.rootFolder = [Folder folder];
             aContainer.rootFolder.objects = objectsBuffer;
             [self.account persist];
-            NSNotification *notification = [NSNotification notificationWithName:@"getObjectsSucceeded" object:self.container userInfo:
-                                            [NSDictionary dictionaryWithObjectsAndKeys:aContainer,@"container",
-                                             self, @"request",nil]];
+            NSNotification *notification = [NSNotification notificationWithName:@"getObjectsSucceeded" object:self.container
+                                                                       userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
+                                                                                 aContainer, @"container",
+                                                                                 self, @"request",
+                                                                                 nil]];
             [[NSNotificationCenter defaultCenter] postNotification:notification];
         } else {
+            SBJSON *parser = [[[SBJSON alloc] init] autorelease];
+            NSArray *jsonObjects = [parser objectWithString:[self responseString]];
             NSString *marker = [[jsonObjects lastObject] objectForKey:@"name"];
             [self.account.manager getObjects:self.container afterMarker:marker objectsBuffer:objectsBuffer];
         }        
     [super failWithError:theError];
 }
 
+#pragma mark - Memory Management
+
 - (void)dealloc {
     [container release];
     [super dealloc];
index af72908..21f1ec1 100644 (file)
 
     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading..."
                                                                                                    andAddToView:self.view];
-    [[self.account.manager  getObjectVersionsList:container object:object]
+    [[self.account.manager getObjectVersionsList:container object:object]
      success:^(OpenStackRequest *request) {
-         SBJSON *parser = [[SBJSON alloc] init];
-         NSArray *versionsInJson = [[parser objectWithString:[request responseString]] objectForKey:@"versions"];
-         for (NSArray *versionInfo in versionsInJson) {
-             [versions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[versionInfo objectAtIndex:0], @"versionID",
-                                 [NSDate dateWithTimeIntervalSince1970:[[versionInfo objectAtIndex:1] doubleValue]], @"versionDate",
-                                 nil]]; 
-         }
-         [parser release];
-         [activityIndicatorView removeFromSuperview];
+         [versions addObjectsFromArray:[request versions]];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          versionsLoaded = YES;
          [self.tableView reloadData];
      }
      failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [self alert:@"Couldn't get versions from server." request:request];
      }];    
 }
     NSString *versionID = [[versionDetails objectForKey:@"versionID"] description];
     [[self.account.manager getObjectInfo:container object:object version:versionID]
      success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          StorageObject *versionedObject = [[[StorageObject alloc] init] autorelease];
          versionedObject.name = object.name;
          versionedObject.fullPath = object.fullPath;
          [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
      } 
      failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [self alert:[NSString stringWithFormat:@"Failed to get file info for version: %@", versionID] request:request];
      }];
 }
index 5fe3c64..ff124c7 100755 (executable)
@@ -6,16 +6,10 @@
 //  The OpenStack project is provided under the Apache 2.0 license.
 //
 
-#import <Foundation/Foundation.h>
-
 @class Provider;
-
 @class AccountManager;
 
 @interface OpenStackAccount : NSObject <NSCoding, NSCopying> {
-    @private
-    BOOL shared;
-    NSString *sharingAccount;
 }
 
 @property (nonatomic, assign) BOOL hasBeenRefreshed;
 @property (nonatomic, retain) NSString *username;
 @property (nonatomic, retain) NSString *apiKey;
 @property (nonatomic, retain) NSString *authToken;
-@property (nonatomic, retain) NSURL *hostURL;
 @property (nonatomic, retain) NSURL *filesURL;
-@property (nonatomic, readonly) NSURL *pithosPublicLinkURLPrefix;
-@property (nonatomic, readonly) NSURL *pithosLoginURLPrefix;
 @property (nonatomic, retain) AccountManager *manager;
-@property (nonatomic, assign) NSInteger containerCount;
 @property (nonatomic, retain) NSNumber *bytesUsed;
 @property (nonatomic, retain) NSNumber *policyQuota;
 @property (nonatomic, retain) NSMutableDictionary *containers;
 @property (nonatomic, assign) BOOL flaggedForDelete;
 @property (nonatomic, assign) BOOL shared;
 @property (nonatomic, retain) NSString *sharingAccount;
+@property (nonatomic, retain) NSMutableDictionary *userCatalog;
 
 + (NSArray *)accounts;
-- (void)persist;
 + (void)persist:(NSArray *)accountArray;
 
+- (void)persist;
 - (NSArray *)pithosSortedContainers;
+- (NSString *)displaynameForUUID:(NSString *)UUID safe:(BOOL)safe;
+- (NSString *)displaynameForUUID:(NSString *)UUID;
 
 @end
index 8e8e0be..a188bfc 100755 (executable)
@@ -20,8 +20,8 @@ static NSArray *accounts = nil;
 @implementation OpenStackAccount
 
 @synthesize uuid, provider, username, filesURL, manager,
-            containerCount, bytesUsed, policyQuota, containers, hasBeenRefreshed, flaggedForDelete,
-            shared, sharingAccount, pithosLoginURLPrefix, pithosPublicLinkURLPrefix, hostURL;
+            bytesUsed, policyQuota, containers, hasBeenRefreshed, flaggedForDelete,
+            shared, sharingAccount, userCatalog;
 
 + (void)initialize {
     accounts = [[Archiver retrieve:@"accounts"] retain];
@@ -31,48 +31,47 @@ static NSArray *accounts = nil;
     }
 }
 
-- (NSArray *)pithosSortedContainers {
-    NSMutableArray *pithosSortedContainers = [NSMutableArray array];
-    if ([self.containers objectForKey:@"pithos"])
-        [pithosSortedContainers addObject:[self.containers objectForKey:@"pithos"]];
-    if ([self.containers objectForKey:@"trash"])
-        [pithosSortedContainers addObject:[self.containers objectForKey:@"trash"]];
-    
-    NSMutableDictionary *otherContainers = [NSMutableDictionary dictionaryWithDictionary:self.containers];
-    [otherContainers removeObjectForKey:@"pithos"];
-    [otherContainers removeObjectForKey:@"trash"];
-    [pithosSortedContainers addObjectsFromArray:[[otherContainers allValues] sortedArrayUsingSelector:@selector(compare:)]];
-    return pithosSortedContainers;
-}
+#pragma mark - Constructors
 
-#pragma mark - Properties
-
-- (NSURL *)pithosPublicLinkURLPrefix {
-    return self.hostURL;
-}
-
-- (NSURL *)pithosLoginURLPrefix {
-    return [self.hostURL URLByAppendingPathComponent:@"login"];
-}
-
-- (void)setShared:(BOOL)aShared {
-    if (shared != aShared) {
-        [self.containers removeAllObjects];
+- (id)init {
+    if ((self = [super init])) {
+        uuid = [[NSString alloc] initWithString:[OpenStackAccount stringWithUUID]];
+        
+        self.userCatalog = [NSMutableDictionary dictionary];
+        
+        manager = [[AccountManager alloc] init];
+        manager.account = self;
     }
-    shared = aShared;
+    return self;
 }
-- (void)setSharingAccount:(NSString *)aSharingAccount {
-    if ((aSharingAccount && ![aSharingAccount isEqualToString:sharingAccount]) || (!aSharingAccount && sharingAccount)) {
-        [self.containers removeAllObjects];
+
+- (id)initWithCoder:(NSCoder *)coder {
+    if ((self = [super init])) {
+        self.uuid = [self decode:coder key:@"uuid"];
+        self.provider = [self decode:coder key:@"provider"];
+        self.username = [self decode:coder key:@"username"];
+        
+        self.filesURL = [self decode:coder key:@"filesURL"];
+        self.bytesUsed = [self decode:coder key:@"bytesUsed"];
+        self.policyQuota = [self decode:coder key:@"policyQuota"];
+        
+//        containers = [self decode:coder key:@"containers"];
+        
+        self.userCatalog = [self decode:coder key:@"userCatalog"];
+        if (!userCatalog) {
+            self.userCatalog = [NSMutableDictionary dictionary];
+        }
+        
+        manager = [[AccountManager alloc] init];
+        manager.account = self;
     }
-    [sharingAccount release];
-    sharingAccount = [aSharingAccount retain];
+    return self;
 }
 
 #pragma mark - Serialization
 
 - (id)copyWithZone:(NSZone *)zone {
-    OpenStackAccount *copy = [[OpenStackAccount allocWithZone:zone] init];
+    OpenStackAccount *copy = [[[self class] allocWithZone:zone] init];
     copy.uuid = self.uuid;
     copy.provider = self.provider;
     copy.username = self.username;
@@ -80,12 +79,13 @@ static NSArray *accounts = nil;
     copy.authToken = self.authToken;
     
     copy.filesURL = self.filesURL;
-    copy.hostURL = self.hostURL;
-    copy.containerCount = self.containerCount;
     copy.bytesUsed = self.bytesUsed;
     copy.policyQuota = self.policyQuota;
-    manager = [[AccountManager alloc] init];
-    manager.account = copy;
+    
+    copy.userCatalog = [[self.userCatalog copy] autorelease];
+    
+    copy.manager = [[[AccountManager alloc] init] autorelease];
+    copy.manager.account = copy;
     return copy;
 }
 
@@ -95,53 +95,73 @@ static NSArray *accounts = nil;
     [coder encodeObject:username forKey:@"username"];
     
     [coder encodeObject:filesURL forKey:@"filesURL"];
-    [coder encodeObject:hostURL forKey:@"hostURL"];
-    [coder encodeInt:containerCount forKey:@"containerCount"];
     [coder encodeObject:bytesUsed forKey:@"bytesUsed"];
     [coder encodeObject:policyQuota forKey:@"policyQuota"];
+    
+//    [coder encodeObject:containers forKey:@"containers"];
+    
+    [coder encodeObject:userCatalog forKey:@"userCatalog"];
 }
 
-- (id)decode:(NSCoder *)coder key:(NSString *)key {    
+- (id)decode:(NSCoder *)coder key:(NSString *)key {
     @try {
-        return [[coder decodeObjectForKey:key] retain];
+        return [coder decodeObjectForKey:key];
     }
     @catch (NSException *exception) {
         return nil;
     }
 }
 
-- (id)initWithCoder:(NSCoder *)coder {
-    if ((self = [super init])) {
-        uuid = [self decode:coder key:@"uuid"];
-        provider = [self decode:coder key:@"provider"];
-        username = [self decode:coder key:@"username"];
-        
-        filesURL = [self decode:coder key:@"filesURL"];
-        hostURL = [self decode:coder key:@"hostURL"];
-        
-        containerCount = [coder decodeIntForKey:@"containerCount"];
+#pragma mark - Properties
 
-        bytesUsed = [self decode:coder key:@"bytesUsed"];
-        policyQuota = [self decode:coder key:@"policyQuota"];
-        
-        containers = [self decode:coder key:@"containers"];
-        
-        manager = [[AccountManager alloc] init];
-        manager.account = self;
+- (void)setShared:(BOOL)aShared {
+    if (shared != aShared) {
+        self.containers = nil;
     }
-    return self;
+    shared = aShared;
 }
 
-- (id)init {
-    if ((self = [super init])) {
-        uuid = [[NSString alloc] initWithString:[OpenStackAccount stringWithUUID]];
-        
-        manager = [[AccountManager alloc] init];
-        manager.account = self;
+- (void)setSharingAccount:(NSString *)aSharingAccount {
+    if ((aSharingAccount && ![aSharingAccount isEqualToString:sharingAccount]) || (!aSharingAccount && sharingAccount)) {
+        self.containers = nil;
     }
-    return self;
+    [sharingAccount release];
+    sharingAccount = [aSharingAccount retain];
+}
+
+// the API key and auth token are stored in the Keychain, so overriding the
+// getter and setter to abstract the encryption away and make it easy to use
+
+- (NSString *)apiKeyKeychainKey {
+    return [NSString stringWithFormat:@"%@-apiKey", self.uuid];
 }
 
+- (NSString *)apiKey {
+    return [Keychain getStringForKey:[self apiKeyKeychainKey]];
+}
+
+- (void)setApiKey:(NSString *)newAPIKey {
+    [Keychain setString:newAPIKey forKey:[self apiKeyKeychainKey]];
+}
+
+- (NSString *)authTokenKeychainKey {
+    return [NSString stringWithFormat:@"%@-authToken", self.uuid];
+}
+
+- (NSString *)authToken {
+    NSString *authToken = [Keychain getStringForKey:[self authTokenKeychainKey]];
+    if (!authToken) {
+        authToken = @"";
+    }
+    return authToken;
+}
+
+- (void)setAuthToken:(NSString *)newAuthToken {
+    [Keychain setString:newAuthToken forKey:[self authTokenKeychainKey]];
+}
+
+#pragma mark - Class Actions
+
 + (NSArray *)accounts {
     if (accounts == nil) {
         accounts = [[Archiver retrieve:@"accounts"] retain];
@@ -156,6 +176,8 @@ static NSArray *accounts = nil;
     accounts = nil;
 }
 
+#pragma mark - Actions
+
 - (void)persist {
     if (!flaggedForDelete) {
         NSMutableArray *accountArr = [NSMutableArray arrayWithArray:[OpenStackAccount accounts]];
@@ -170,46 +192,42 @@ static NSArray *accounts = nil;
                 break;
             }
         }
-            
+        
         if (!accountPresent) {
             [accountArr insertObject:self atIndex:0];
         }
         
-        [Archiver persist:[NSArray arrayWithArray:accountArr] key:@"accounts"];    
+        [Archiver persist:[NSArray arrayWithArray:accountArr] key:@"accounts"];
         [accounts release];
         accounts = nil;
     }
 }
 
-// the API key and auth token are stored in the Keychain, so overriding the 
-// getter and setter to abstract the encryption away and make it easy to use
-
-- (NSString *)apiKeyKeychainKey {
-    return [NSString stringWithFormat:@"%@-apiKey", self.uuid];
-}
-
-- (NSString *)apiKey {    
-    return [Keychain getStringForKey:[self apiKeyKeychainKey]];
-}
-
-- (void)setApiKey:(NSString *)newAPIKey {
-    [Keychain setString:newAPIKey forKey:[self apiKeyKeychainKey]];
-}
-
-- (NSString *)authTokenKeychainKey {
-    return [NSString stringWithFormat:@"%@-authToken", self.uuid];
+- (NSArray *)pithosSortedContainers {
+    NSMutableArray *pithosSortedContainers = [NSMutableArray array];
+    if ([self.containers objectForKey:@"pithos"])
+        [pithosSortedContainers addObject:[self.containers objectForKey:@"pithos"]];
+    if ([self.containers objectForKey:@"trash"])
+        [pithosSortedContainers addObject:[self.containers objectForKey:@"trash"]];
+    
+    NSMutableDictionary *otherContainers = [NSMutableDictionary dictionaryWithDictionary:self.containers];
+    [otherContainers removeObjectForKey:@"pithos"];
+    [otherContainers removeObjectForKey:@"trash"];
+    [pithosSortedContainers addObjectsFromArray:[[otherContainers allValues] sortedArrayUsingSelector:@selector(compare:)]];
+    return pithosSortedContainers;
 }
 
-- (NSString *)authToken {
-    NSString *authToken = [Keychain getStringForKey:[self authTokenKeychainKey]];
-    if (!authToken) {
-        authToken = @"";
+- (NSString *)displaynameForUUID:(NSString *)UUID safe:(BOOL)safe {
+    NSString *displayName = [userCatalog objectForKey:UUID];
+    if (safe && !displayName) {
+        return UUID;
+    } else {
+        return displayName;
     }
-    return authToken;
 }
 
-- (void)setAuthToken:(NSString *)newAuthToken {
-    [Keychain setString:newAuthToken forKey:[self authTokenKeychainKey]];
+- (NSString *)displaynameForUUID:(NSString *)UUID {
+    return [self displaynameForUUID:UUID safe:NO];
 }
 
 #pragma mark - Memory Management
@@ -219,12 +237,12 @@ static NSArray *accounts = nil;
     [manager release];
     [provider release];
     [username release];
-    [hostURL release];
     [filesURL release];
     [containers release];
     [sharingAccount release];
     [bytesUsed release];
     [policyQuota release];
+    [userCatalog release];
     
     [super dealloc];
 }
index cffed89..0d1a4a5 100755 (executable)
 #pragma mark - Persistence
 
 - (NSString *)cacheFilePathForHash:(NSString *)hash {
+    if (!hash)
+        return nil;
     NSString *filePath = [self.cachedObjectsDictionary objectForKey:hash];
     if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
         @synchronized(self.cachedObjectsDictionary) {
index ca7210a..4bcc0bb 100755 (executable)
@@ -8,75 +8,56 @@
 
 #import "ASIHTTPRequest.h"
 
-#define kOpenStackPollingFrequency 20.0
-
 @class OpenStackAccount, Container, StorageObject, APICallback, ErrorAlerter;
 
 @interface OpenStackRequest : ASIHTTPRequest {
     OpenStackAccount *account;
-    BOOL retried;
-    OpenStackRequest *retriedRequest;
     ASIBasicBlock backupCompletionBlock;
     ASIBasicBlock backupFailureBlock;
     APICallback *callback;
-    NSInteger retriedCount;
 }
 
 @property (nonatomic, retain) OpenStackAccount *account;
 @property (nonatomic, retain) APICallback *callback;
-@property (nonatomic, assign) NSInteger retriedCount;
 @property (nonatomic, retain) ErrorAlerter *errorAlerter;
 
-- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock;
-- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock;
-
-- (BOOL)isSuccess;
-- (void)notify;
-- (void)notify:(NSString *)name;
-
 + (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url;
-+ (id)getSharingAccountsRequest:(OpenStackAccount *)account;
 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path;
 
-#pragma mark - Authentication
++ (id)userCatalogRequest:(OpenStackAccount *)account displaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs;
+- (NSDictionary *)catalogs;
+- (NSDictionary *)displaynameCatalog;
+- (NSDictionary *)UUIDCatalog;
 
-+ (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account;
-
-#pragma mark - Object Storage Requests
++ (id)authenticationRequest:(OpenStackAccount *)account;
++ (id)getSharingAccountsRequest:(OpenStackAccount *)account;
+- (NSArray *)sharingAccounts;
 
-+ (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account;
-+ (OpenStackRequest *)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container;
-+ (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account;
++ (id)getStorageAccountInfoRequest:(OpenStackAccount *)account;
++ (id)getContainersRequest:(OpenStackAccount *)account;
 - (NSMutableDictionary *)containers;
++ (id)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo;
 
-+ (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container;
-+ (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container;
-
-+ (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container;
++ (id)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container;
++ (id)createContainerRequest:(OpenStackAccount *)account container:(Container *)container;
++ (id)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container;
++ (id)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container;
 - (NSMutableDictionary *)objects;
++ (id)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container;
+
++ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
++ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version;
++ (id)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
+- (NSMutableArray *)versions;
++ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
++ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version;
++ (id)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
++ (id)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
++ (id)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
 
-+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
-+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account
-                                 container:(Container *)container
-                                    object:(StorageObject *)object
-                                   version:(NSString *)version;
-+ (OpenStackRequest *)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
-+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
-+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account
-                             container:(Container *)container
-                                object:(StorageObject *)object
-                               version:(NSString *)version;
-+ (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
-+ (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
-+ (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
-
-#pragma mark - Container Write Requests
-
-+ (OpenStackRequest *)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container;
-
-#pragma mark - Account Write Requests
-
-+ (OpenStackRequest *)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo;
+- (BOOL)isSuccess;
 
+- (void)notify;
+- (void)notify:(NSString *)name;
 
 @end
index 8a0b233..692c4fd 100755 (executable)
 #import "APILogEntry.h"
 #import "NSString+Conveniences.h"
 
-static NSRecursiveLock *accessDetailsLock = nil;
-
 @implementation OpenStackRequest
 
-@synthesize account, callback, retriedCount, errorAlerter;
-
-- (BOOL)isSuccess {
-       return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
-}
-
-- (void)notify:(NSString *)name {
-    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
-    NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo];
-    [[NSNotificationCenter defaultCenter] postNotification:notification];
-}
-
-- (void)notify {
-    NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description]];
-    NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description], self.callback.uuid];
-
-    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
-
-    NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
-    [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
-
-    NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
-    [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
-    
-}
-
-- (NSString *)responseString {
-    if (retried) {
-        return [retriedRequest responseString];
-    } else {
-        return [super responseString];
-    }
-}
-
-- (NSData *)responseData {
-    if (retried) {
-        return [retriedRequest responseData];
-    } else {
-        return [super responseData];
-    }
-}
-
-- (NSDictionary *)responseHeaders {
-    if (retried) {
-        return [retriedRequest responseHeaders];
-    } else {
-        return [super responseHeaders];
-    }
-}
-
-- (int)responseStatusCode {
-    if (retried) {
-        return [retriedRequest responseStatusCode];
-    } else {
-        return [super responseStatusCode];
-    }
-}
-
-- (NSString *)responseStatusMessage {
-    if (retried) {
-        return [retriedRequest responseStatusMessage];
-    } else {
-        return [super responseStatusMessage];
-    }
-}
-
-- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
-    [super setCompletionBlock:aCompletionBlock];
-    [backupCompletionBlock release];
-    backupCompletionBlock = [aCompletionBlock copy];
-}
-
-- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
-    [super setFailedBlock:aFailedBlock];
-    [backupFailureBlock release];
-    backupFailureBlock = [aFailedBlock copy];
-}
-
-#pragma mark -
-#pragma mark Generic Constructors
+@synthesize account, callback, errorAlerter;
 
-+ (void)initialize {
-       if (self == [OpenStackRequest class]) {
-               accessDetailsLock = [[NSRecursiveLock alloc] init];
-       }
-}
+#pragma mark - Constructors
+#pragma mark Generic
 
 + (id)request:(OpenStackAccount *)account method:(NSString *)method url:(NSURL *)url {
-       OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:url] autorelease];
+       OpenStackRequest *request = [[[self alloc] initWithURL:url] autorelease];
     request.account = account;
-       [request setRequestMethod:method];
-       [request addRequestHeader:@"X-Auth-Token" value:[account authToken]];
+       request.requestMethod = method;
+       [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
     [request addRequestHeader:@"Content-Type" value:@"application/json"];
-    [request setTimeOutSeconds:60];
-    [request setNumberOfTimesToRetryOnTimeout:5];
-    request.retriedCount = 0;
+    request.timeOutSeconds = 60;
+    request.numberOfTimesToRetryOnTimeout = 5;
        return request;
 }
 
-+ (id)getSharingAccountsRequest:(OpenStackAccount *)account {
-    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json", account.provider.authEndpointURL]];
-    return [OpenStackRequest request:account method:@"GET" url:url];
-}
-
 + (id)filesRequest:(OpenStackAccount *)account method:(NSString *)method path:(NSString *)path {
     NSString *urlString = [account.filesURL description];
     if (account.sharingAccount) {
-        NSRange authUserRange = [urlString rangeOfString:account.username];
-        urlString = [NSString stringWithFormat:@"%@%@", [urlString substringToIndex:authUserRange.location], account.sharingAccount];
+        urlString = [NSString stringWithFormat:@"%@%@",
+                     [urlString substringToIndex:[urlString rangeOfString:account.username].location],
+                     account.sharingAccount];
     }
-    
        NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@?format=json%@",
                                        urlString,
                                        path,
-                                       account.shared ? @"&shared=" : @""]];
-    
-    return [OpenStackRequest request:account method:method url:url];
-}
-
-#pragma mark -
-#pragma mark Auth Retry
-
-- (void)authRetrySucceded:(OpenStackRequest *)retryRequest {
-    self.account.authToken = [[retryRequest responseHeaders] objectForKey:@"X-Auth-Token"]; 
-    [self.account persist];
-    
-    // try the original request again!
-    retried = YES;
-    retriedRequest = [self copy];
-
-       [retriedRequest addRequestHeader:@"X-Auth-Token" value:self.account.authToken];    
-    
-    if (backupCompletionBlock) {
-        [retriedRequest setCompletionBlock:^{
-            backupCompletionBlock();
-        }];
+                                       (account.shared ? @"&shared=" : @"")]];
+    return [self request:account method:method url:url];
+}
+
+#pragma mark User Catalog
++ (id)userCatalogRequest:(OpenStackAccount *)account displaynames:(NSArray *)displaynames UUIDs:(NSArray *)UUIDs {
+    OpenStackRequest *request = [self request:account method:@"POST" url:account.provider.userCatalogURL];
+    NSMutableString *dataString = [NSMutableString stringWithString:@"{\"displaynames\":["];
+    if (displaynames) {
+        for (NSUInteger index = 0 ; index < displaynames.count ; index++) {
+            [dataString appendFormat:@"\"%@\"%@",
+             [[[displaynames objectAtIndex:index] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]
+              stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""],
+             ((index == displaynames.count - 1) ? @"" : @",")];
+        }
     }
-    if (backupFailureBlock) {
-        [retriedRequest setFailedBlock:^{
-            backupFailureBlock();
-        }];
+    [dataString appendFormat:@"],\"uuids\":["];
+    if (UUIDs) {
+        for (NSUInteger index = 0 ; index < UUIDs.count ; index++) {
+            [dataString appendFormat:@"\"%@\"%@", [UUIDs objectAtIndex:index], ((index == UUIDs.count - 1) ? @"" : @",")];
+        }
     }
-
-    [retriedRequest startSynchronous];     
+    [dataString appendFormat:@"]}"];
+    [request appendPostData:[dataString dataUsingEncoding:NSUTF8StringEncoding]];
+    return request;
 }
 
-- (void)authRetryFailed:(OpenStackRequest *)retryRequest {
-    // if it fails due to bad connection, try again?
-    NSNotification *notification = [NSNotification notificationWithName:[self.account.manager notificationName:@"authRetryFailed" identifier:0] object:nil userInfo:[NSDictionary dictionaryWithObject:retryRequest forKey:@"request"]];
-    [[NSNotificationCenter defaultCenter] postNotification:notification];
+- (NSDictionary *)catalogs {
+    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
+    NSDictionary *catalogs = [parser objectWithString:[self responseString]];
+    return catalogs;
 }
 
-#pragma mark -
-#pragma mark ASIHTTPRequest Overrides
-
-- (void)failWithError:(NSError *)theError {
-    if (responseStatusCode == 401 && ![url isEqual:account.provider.authEndpointURL]) {
-        // auth is expired, so get a fresh token
-        if (account && ![account.provider isGRNet]) {
-            OpenStackRequest *retryRequest = [OpenStackRequest authenticationRequest:account];
-            retryRequest.delegate = self;
-            retryRequest.didFinishSelector = @selector(authRetrySucceded:);
-            retryRequest.didFailSelector = @selector(authRetryFailed:);
-            [retryRequest startSynchronous];
-        }
-    } else if (responseStatusCode == 503) {        
-        NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
-        [[NSNotificationCenter defaultCenter] postNotification:notification];        
-//        [super failWithError:theError];
-    } else if (responseStatusCode == 0) {
-        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
-        if (![defaults boolForKey:@"already_failed_on_connection"]) {
-            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
-            [alert show];
-            [alert release];
-        }
-        [defaults setBool:YES forKey:@"already_failed_on_connection"];
-        [defaults synchronize];              
-    }
-
-    [super failWithError:theError];
+- (NSDictionary *)displaynameCatalog {
+    return [[self catalogs] objectForKey:@"displayname_catalog"];
 }
 
-#pragma mark - Authentication
+- (NSDictionary *)UUIDCatalog {
+    return [[self catalogs] objectForKey:@"uuid_catalog"];
+}
 
-+ (OpenStackRequest *)authenticationRequest:(OpenStackAccount *)account {
+#pragma mark Top
 
-       OpenStackRequest *request = [[[OpenStackRequest alloc] initWithURL:account.provider.authEndpointURL] autorelease];
++ (id)authenticationRequest:(OpenStackAccount *)account {
+       OpenStackRequest *request = [[[self alloc] initWithURL:account.provider.authEndpointURL] autorelease];
     request.account = account;
+    request.requestMethod = @"GET";
     [request addRequestHeader:@"X-Auth-User" value:account.username];
-    if (account.authToken) {
-        [request addRequestHeader:@"X-Auth-Token" value:account.authToken];
-    } else {
-        [request addRequestHeader:@"X-Auth-Token" value:@""];
-    }
-
+    [request addRequestHeader:@"X-Auth-Token" value:(account.authToken ? account.authToken : @"")];
+    request.timeOutSeconds = 60;
+    request.numberOfTimesToRetryOnTimeout = 5;
        return request;
 }
 
-#pragma mark -
-#pragma mark Object Storage Requests
++ (id)getSharingAccountsRequest:(OpenStackAccount *)account {
+    NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"%@?format=json", account.provider.authEndpointURL]];
+    return [self request:account method:@"GET" url:url];
+}
+
+- (NSArray *)sharingAccounts {
+    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
+    NSArray *sharingAccounts = [parser objectWithString:[self responseString]];
+    return sharingAccounts;
+}
+
+#pragma mark Account
 
-+ (OpenStackRequest *)getStorageAccountInfoRequest:(OpenStackAccount *)account {
-    return [OpenStackRequest filesRequest:account method:@"HEAD" path:@""];
++ (id)getStorageAccountInfoRequest:(OpenStackAccount *)account {
+    return [self filesRequest:account method:@"HEAD" path:@""];
 }
 
-+ (OpenStackRequest *)getContainersRequest:(OpenStackAccount *)account {
-    return [OpenStackRequest filesRequest:account method:@"GET" path:@""];
++ (id)getContainersRequest:(OpenStackAccount *)account {
+    return [self filesRequest:account method:@"GET" path:@""];
 }
 
 - (NSMutableDictionary *)containers {
-    SBJSON *parser = [[SBJSON alloc] init];
+    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
-    NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
-    
-    for (int i = 0; i < [jsonObjects count]; i++) {
-        NSDictionary *dict = [jsonObjects objectAtIndex:i];
+    NSMutableDictionary *containers = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
+    for (NSDictionary *dict in jsonObjects) {
         Container *container = [Container fromJSON:dict];
-        [objects setObject:container forKey:container.name];
+        [containers setObject:container forKey:container.name];
+    }
+    return containers;
+}
+
++ (id)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo {
+    OpenStackRequest *request = [self filesRequest:account method:@"POST" path:@""];
+
+    NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
+    if (groups.count) {
+        for (NSString *groupName in groups) {
+            [request.requestHeaders setObject:[NSString encodeToPercentEscape:[groups objectForKey:groupName]]
+                                       forKey:[NSString stringWithFormat:@"X-Account-Group-%@", [NSString encodeToPercentEscape:groupName]]];
+        }
+    } else {
+        [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
     }
     
-    [parser release];
-    return objects;
+    NSMutableDictionary *metadata = [accountInfo objectForKey:@"metadata"];
+    for (NSString *key in metadata) {
+        [request.requestHeaders setObject:[NSString encodeToPercentEscape:[metadata objectForKey:key]]
+                                   forKey:[NSString stringWithFormat:@"X-Account-Meta-%@", [NSString encodeToPercentEscape:key]]];
+    }
+    
+    return request;
+}
+
+#pragma mark Container
+
++ (id)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container {
+    return [self filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
 }
 
-+ (OpenStackRequest *)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {    
-    return [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
++ (id)createContainerRequest:(OpenStackAccount *)account container:(Container *)container {
+    return [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
 }
 
-+ (OpenStackRequest *)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
-    return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
++ (id)deleteContainerRequest:(OpenStackAccount *)account container:(Container *)container {
+    return [self filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
 }
 
-+ (OpenStackRequest *)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
-    return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];    
++ (id)getObjectsRequest:(OpenStackAccount *)account container:(Container *)container {
+    return [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
 }
 
 - (NSMutableDictionary *)objects {
-    SBJSON *parser = [[SBJSON alloc] init];
+    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
     NSArray *jsonObjects = [parser objectWithString:[self responseString]];
-
-    NSMutableDictionary *objects = [[[NSMutableDictionary alloc] initWithCapacity:[jsonObjects count]] autorelease];
-    
-    for (int i = 0; i < [jsonObjects count]; i++) {
-        NSDictionary *dict = [jsonObjects objectAtIndex:i];
+    NSMutableDictionary *objects = [NSMutableDictionary dictionaryWithCapacity:[jsonObjects count]];
+    for (NSDictionary *dict in jsonObjects) {
         StorageObject *object = [StorageObject fromJSON:dict];
         [objects setObject:object forKey:object.name];
     }
-    
-    [parser release];
     return objects;
 }
 
-+ (OpenStackRequest *)getContainerInfoRequest:(OpenStackAccount *)account container:(Container *)container {
-    return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@",[NSString encodeToPercentEscape:container.name]]];    
++ (id)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container {
+    OpenStackRequest *request = [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
+    [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
+    [request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"];
+    return request;
 }
 
-+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
+#pragma mark Storage Object
+
++ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
     NSString *objectFullPath = object.fullPath;
     if ([objectFullPath hasPrefix:@"/"])
         objectFullPath = [objectFullPath substringFromIndex:1];
-    return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
+    return [self filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
 }
 
-+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account
-                                 container:(Container *)container
-                                    object:(StorageObject *)object
-                                   version:(NSString *)version {
-    OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:account container:container object:object];
-    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
++ (id)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version {
+    OpenStackRequest *request = [self getObjectInfoRequest:account container:container object:object];
+    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]];
     return request;
 }
 
-+ (OpenStackRequest *)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
-    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
-    
-    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list",request.url.description]];
++ (id)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
+    OpenStackRequest *request = [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
+    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list", request.url.description]];
     return request;
 }
 
-+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
-    return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
+- (NSMutableArray *)versions {
+    SBJSON *parser = [[[SBJSON alloc] init] autorelease];
+    NSArray *jsonVersions = [[parser objectWithString:[self responseString]] objectForKey:@"versions"];
+    NSMutableArray *versions = [NSMutableArray arrayWithCapacity:[jsonVersions count]];
+    for (NSArray *jsonVersion in jsonVersions) {
+        [versions addObject:[NSDictionary dictionaryWithObjectsAndKeys:
+                             [jsonVersion objectAtIndex:0], @"versionID",
+                             [NSDate dateWithTimeIntervalSince1970:[[jsonVersion objectAtIndex:1] doubleValue]], @"versionDate",
+                             nil]];
+    }
+    return versions;
 }
 
-+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account
-                             container:(Container *)container
-                                object:(StorageObject *)object
-                               version:(NSString *)version {
-    OpenStackRequest *request = [OpenStackRequest getObjectRequest:account container:container object:object];
-    if (version) {
-        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
-    }
+
++ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
+    return [self filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
+}
+
++ (id)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object version:(NSString *)version {
+    OpenStackRequest *request = [self getObjectRequest:account container:container object:object];
+    if (version)
+        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@", request.url.description, version]];
     return request;
 }
 
-+ (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
-    NSString *fullPath = object.fullPath;
-    if ([fullPath characterAtIndex:0] == '/') {
-        fullPath = [fullPath substringFromIndex:1];
-    }
-        
-    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
-    
++ (id)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
+    NSString *objectFullPath = object.fullPath;
+    if ([objectFullPath hasPrefix:@"/"])
+        objectFullPath = [objectFullPath substringFromIndex:1];
+    OpenStackRequest *request = [self filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
+
     if (object.sharing)
         [request.requestHeaders setObject:object.sharing forKey:@"X-Object-Sharing"];
-    
+
     NSString *metadataKeyHeaderPrefix;
-    if ([fullPath length] == 0)
+    if ([objectFullPath length] == 0)
         metadataKeyHeaderPrefix = @"X-Container-Meta-";
     else
         metadataKeyHeaderPrefix = @"X-Object-Meta-";
-    
     for (NSString *metadataKey in object.metadata) {
-        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
+        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey];
         metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
         NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
         [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
     }
-
+    
        [request setPostBody:[NSMutableData dataWithData:object.data]];
-    [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];    
+    [request.requestHeaders setObject:object.contentType forKey:@"Content-Type"];
        return request;
 }
 
-+ (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
-    NSString *fullPath = object.fullPath;
-    if ([fullPath length] != 0 && [fullPath characterAtIndex:0] == '/') {
-        fullPath = [fullPath substringFromIndex:1];
-    }
-
++ (id)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
+    NSString *objectFullPath = object.fullPath;
+    if ([objectFullPath hasPrefix:@"/"])
+        objectFullPath = [objectFullPath substringFromIndex:1];
+    OpenStackRequest *request = [self filesRequest:account method:@"POST" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
+    
     NSString *metadataKeyHeaderPrefix;
-    if ([fullPath length] == 0)
+    if ([objectFullPath length] == 0)
         metadataKeyHeaderPrefix = @"X-Container-Meta-";
     else
         metadataKeyHeaderPrefix = @"X-Object-Meta-";
-        
-    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]]; 
-
     for (NSString *metadataKey in object.metadata) {
-        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey]; 
+        NSString *metadataKeyHeader = [NSString stringWithFormat:@"%@%@", metadataKeyHeaderPrefix, metadataKey];
         metadataKeyHeader = [NSString encodeToPercentEscape:metadataKeyHeader];
         NSString *metadataValue = [NSString encodeToPercentEscape:[object.metadata objectForKey:metadataKey]];
         [request.requestHeaders setObject:metadataValue forKey:metadataKeyHeader];
     }
-    if (!account.sharingAccount) {
-        NSString *objectIsPublic = ([object.publicURI length] > 0) ? @"true" : @"false";
-        [request.requestHeaders setObject:objectIsPublic forKey:@"X-Object-Public"];
     
+    if (!account.sharingAccount) {
+        [request.requestHeaders setObject:([object.publicURI length] ? @"true" : @"false") forKey:@"X-Object-Public"];
         if (object.sharing) {
-            NSString *urlEncodedSharingString = [NSString encodeToPercentEscape:object.sharing];
-            [request.requestHeaders setObject:urlEncodedSharingString forKey:@"X-Object-Sharing"];
+            [request.requestHeaders setObject:[NSString encodeToPercentEscape:object.sharing] forKey:@"X-Object-Sharing"];
         }
     }
     return request;
 }
 
-+ (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
-    if ([object.fullPath characterAtIndex:0] == '/') {
-        return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
-    } else {
-        return [OpenStackRequest filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
-    }
++ (id)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
+    NSString *objectFullPath = object.fullPath;
+    if ([objectFullPath hasPrefix:@"/"])
+        objectFullPath = [objectFullPath substringFromIndex:1];
+    return [self filesRequest:account method:@"DELETE" path:[NSString stringWithFormat:@"/%@/%@", [NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:objectFullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
 }
 
-+ (OpenStackRequest *)writeContainerPolicyRequest:(OpenStackAccount *)account container:(Container *)container {
-    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"PUT" path:[NSString stringWithFormat:@"/%@", [NSString encodeToPercentEscape:container.name]]];
-    
-    [request.requestHeaders setObject:container.versioning forKey:@"X-Container-Policy-Versioning"];
-    [request.requestHeaders setObject:[NSString stringWithFormat:@"%u", container.quota] forKey:@"X-Container-Policy-Quota"];
-    
-    return request;
+#pragma mark - ASIHTTPRequest Overrides
+
+- (void)setCompletionBlock:(ASIBasicBlock)aCompletionBlock {
+    [super setCompletionBlock:aCompletionBlock];
+    [backupCompletionBlock release];
+    backupCompletionBlock = [aCompletionBlock copy];
 }
 
-+ (OpenStackRequest *)writeAccountMetadataRequest:(OpenStackAccount *)account withAccountInfo:(NSDictionary *)accountInfo {
-    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"POST" path:@""];
-    
-    NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
-    for (NSString *groupName in groups) {
-        NSString *group = [NSString encodeToPercentEscape:[groups objectForKey:groupName]];
-        groupName = [NSString encodeToPercentEscape:groupName];
-        [request.requestHeaders setObject:group forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]];
+- (void)setFailedBlock:(ASIBasicBlock)aFailedBlock {
+    [super setFailedBlock:aFailedBlock];
+    [backupFailureBlock release];
+    backupFailureBlock = [aFailedBlock copy];
+}
+
+- (void)failWithError:(NSError *)theError {
+    if (responseStatusCode == 503) {
+        NSNotification *notification = [NSNotification notificationWithName:@"serviceUnavailable" object:nil userInfo:nil];
+        [[NSNotificationCenter defaultCenter] postNotification:notification];
+    } else if (responseStatusCode == 0) {
+        NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
+        if (![defaults boolForKey:@"already_failed_on_connection"]) {
+            UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Connection Error" message:@"Please check your connection or API URL and try again." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
+            [alert show];
+            [alert release];
+        }
+        [defaults setBool:YES forKey:@"already_failed_on_connection"];
+        [defaults synchronize];
     }
-    if ([groups count] == 0)
-        [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
     
-    NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"];
-    for (NSString *metadataKey in accountMetadata) {
-        NSString *metadataValue = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
-        metadataKey = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
-        [request.requestHeaders setObject:metadataValue forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]];
-    }
+    [super failWithError:theError];
+}
+
+#pragma mark - Actions
+
+- (BOOL)isSuccess {
+       return (200 <= [self responseStatusCode]) && ([self responseStatusCode] <= 299);
+}
+
+#pragma mark - Notifications
+
+- (void)notify:(NSString *)name {
+    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
+    NSNotification *notification = [NSNotification notificationWithName:name object:nil userInfo:callbackUserInfo];
+    [[NSNotificationCenter defaultCenter] postNotification:notification];
+}
+
+- (void)notify {
+    NSString *observeName = [NSString stringWithFormat:@"%@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description]];
+    NSString *callbackName = [NSString stringWithFormat:@"%@ %@ %@ %@", [self isSuccess] ? @"SUCCESS" : @"FAILURE", self.requestMethod, [self.url description], self.callback.uuid];
+
+    NSDictionary *callbackUserInfo = [NSDictionary dictionaryWithObject:self forKey:@"response"];
+
+    NSNotification *observeNotification = [NSNotification notificationWithName:observeName object:nil userInfo:callbackUserInfo];
+    [[NSNotificationCenter defaultCenter] postNotification:observeNotification];
+
+    NSNotification *callbackNotification = [NSNotification notificationWithName:callbackName object:nil userInfo:callbackUserInfo];
+    [[NSNotificationCenter defaultCenter] postNotification:callbackNotification];
     
-    return request;
 }
 
-#pragma mark -
-#pragma mark Memory Management
+#pragma mark - Memory Management
 
 - (void)releaseBackupBlocksOnMainThread {
        NSMutableArray *blocks = [NSMutableArray array];
index eb51043..4584c95 100755 (executable)
@@ -29,6 +29,4 @@
 - (void)hideToolbarActivityMessage;
 - (void)hideToolbarInfoMessage;
 
-- (void)addHomeButton;
-
 @end
index 44669fc..e9db7fb 100755 (executable)
@@ -23,6 +23,7 @@
 }
 
 - (void)viewDidLoad {
+    [super viewDidLoad];
     if (self.navigationController.navigationBar) {
         if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) {
             self.toolbar.tintColor = self.navigationController.navigationBar.tintColor;
@@ -34,9 +35,6 @@
             self.toolbar.opaque = NO;            
         }
     }
-    
-    toolbarProgressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
-    toolbarProgressView.frame = CGRectMake(0.0, 24.0, 185.0, 10.0);
 }
 
 #pragma mark - Internal
             
             UIView *labelWithProgress = [[[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 185.0, 40.0)] autorelease];
             [labelWithProgress addSubview:toolbarLabel];
+            if (!toolbarProgressView) {
+                toolbarProgressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar];
+                toolbarProgressView.frame = CGRectMake(0.0, 24.0, 185.0, 10.0);
+            }
             [labelWithProgress addSubview:toolbarProgressView];
             toolbarLabelItem = [[UIBarButtonItem alloc] initWithCustomView:labelWithProgress];
             
     }
 }
 
-- (void)addHomeButton {
-    UIImage *buttonImage = [UIImage imageNamed:@"HomeFolderIcon.png"];
-       
-    UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
-    [button setImage:buttonImage forState:UIControlStateNormal];       
-    button.frame = CGRectMake(0, 0, buttonImage.size.width, buttonImage.size.height);
-       [button addTarget:self action:@selector(homeButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
-    UIBarButtonItem *customBarItem = [[UIBarButtonItem alloc] initWithCustomView:button];
-    NSMutableArray *toolbarItems = [NSMutableArray arrayWithArray:self.toolbar.items];
-    [toolbarItems addObject:customBarItem];
-    self.toolbar.items = toolbarItems;
-    [customBarItem release];
-}
-
 - (void)dealloc {
-    if (toolbarActivityMessageVisible)
-        [self hideToolbarActivityMessage];
-    if (toolbarInfoMessageVisible)
-        [self hideToolbarInfoMessage];
+    [self hideToolbarActivityMessage];
+    [self hideToolbarInfoMessage];
     [toolbarProgressView release];
     [super dealloc];
 }
index 8f06fdd..f7fbea7 100755 (executable)
@@ -6,20 +6,21 @@
 //  The OpenStack project is provided under the Apache 2.0 license.
 //
 
-#import <Foundation/Foundation.h>
-
 @interface Provider : NSObject <NSCoding> {
-    // the name of the provider (example: okeanos)
     NSString *name;
-    
-    // endpoint for authentication (example: https://pithos.okeanos.grnet.gr )
-    NSURL *authEndpointURL;
+    NSURL *hostURL;
+    NSString *version;
 }
 
 + (NSArray *)providers;
-- (BOOL)isGRNet;
 
 @property (nonatomic, retain) NSString *name;
-@property (nonatomic, retain) NSURL *authEndpointURL;
+@property (nonatomic, retain) NSURL *hostURL;
+@property (nonatomic, retain) NSString *version;
+
+@property (nonatomic, readonly) NSURL *authEndpointURL;
+@property (nonatomic, readonly) NSURL *loginURL;
+@property (nonatomic, readonly) NSURL *userCatalogURL;
+@property (nonatomic, readonly) NSURL *publicLinkURLPrefix;
 
 @end
index 6ae2e1f..a0fc530 100755 (executable)
@@ -12,21 +12,14 @@ static NSArray *providers = nil;
 
 @implementation Provider
 
-@synthesize name, authEndpointURL;
-
-+ (void)initialize {
-    Provider *provider = [[Provider alloc] init];
-    provider.name = @"okeanos";
-    provider.authEndpointURL = [NSURL URLWithString:@"https://pithos.okeanos.grnet.gr/v1"];
-    providers = [[NSArray alloc] initWithObjects:provider, nil];
-    [provider release];
-}
+@synthesize name, hostURL, version, authEndpointURL, loginURL, userCatalogURL, publicLinkURLPrefix;
 
 + (NSArray *)providers {
     if (providers == nil) {
         Provider *provider = [[Provider alloc] init];
         provider.name = @"okeanos";
-        provider.authEndpointURL = [NSURL URLWithString:@"https://pithos.okeanos.grnet.gr/v1"];
+        provider.hostURL = [NSURL URLWithString:@"https://pithos.okeanos.grnet.gr"];
+        provider.version = @"v1";
         providers = [[NSArray alloc] initWithObjects:provider, nil];
         [provider release];
     }
@@ -36,31 +29,59 @@ static NSArray *providers = nil;
 
 #pragma mark - Serialization
 
-- (void)encodeWithCoder: (NSCoder *)coder {
+- (void)encodeWithCoder:(NSCoder *)coder {
     [coder encodeObject:name forKey:@"name"];
-    [coder encodeObject:authEndpointURL forKey:@"authEndpointURL"];
+    [coder encodeObject:hostURL forKey:@"hostURL"];
+    [coder encodeObject:version forKey:@"version"];
 }
 
 - (id)initWithCoder:(NSCoder *)coder {
     if ((self = [super init])) {
-        name = [[coder decodeObjectForKey:@"name"] retain];
-        authEndpointURL = [[coder decodeObjectForKey:@"authEndpointURL"] retain];
+        self.name = [coder decodeObjectForKey:@"name"];
+        self.hostURL = [coder decodeObjectForKey:@"hostURL"];
+        self.version = [coder decodeObjectForKey:@"version"];
+        
+        // Support for older versions.
+        if (!hostURL) {
+            NSURL *tmpURL = [coder decodeObjectForKey:@"authEndpointURL"];
+            if (tmpURL) {
+                self.hostURL = [tmpURL URLByDeletingLastPathComponent];
+            } else {
+                self.hostURL = [NSURL URLWithString:@"https://pithos.okeanos.grnet.gr"];
+            }
+        }
+        if (!version) {
+            self.version = @"v1";
+        }
     }
     return self;
 }
 
-#pragma mark - HTTP Logo Requests
-
-- (BOOL)isGRNet {
-    return ([self.authEndpointURL.host rangeOfString:@"grnet.gr"].location != NSNotFound);
-}
-
 #pragma mark - Memory Management
 
 - (void)dealloc {
     [name release];
-    [authEndpointURL release];
+    [hostURL release];
+    [version release];
     [super dealloc];
 }
 
+#pragma mark - Properties
+
+- (NSURL *)authEndpointURL {
+    return [hostURL URLByAppendingPathComponent:version];
+}
+
+- (NSURL *)loginURL {
+    return [hostURL URLByAppendingPathComponent:@"login"];
+}
+
+- (NSURL *)userCatalogURL {
+    return [hostURL URLByAppendingPathComponent:@"user_catalogs"];
+}
+
+- (NSURL *)publicLinkURLPrefix {
+    return hostURL;
+}
+
 @end
index d6c1725..cf13aa9 100755 (executable)
@@ -54,7 +54,7 @@
             // Since most people will just have one account this is preferred.
             [self presentAccountHomeViewControllerForAccount:[[OpenStackAccount accounts] objectAtIndex:0] animated:NO];
             AccountHomeViewController *ahvc = [[self.navigationController viewControllers] lastObject];
-            ahvc.navigationItem.title = ahvc.account.username;
+            ahvc.navigationItem.title = [ahvc.account displaynameForUUID:ahvc.account.username safe:YES];
             [ahvc presentContainersViewControllerShared:NO Animated:NO];
         } else {
             // If there are no accounts, go straight to the add account screen on launch.
     
        // Configure the cell.
     OpenStackAccount *account = [[OpenStackAccount accounts] objectAtIndex:indexPath.row];
-    cell.textLabel.text = account.username;
+    cell.textLabel.text = [account displaynameForUUID:account.username safe:YES];
     cell.detailTextLabel.text = account.provider.name;
     cell.imageView.image = [UIImage imageNamed:@"pithos-solo-smallest.png"];
     
index d6d3bfa..2acffc4 100644 (file)
@@ -54,5 +54,6 @@
 @property (nonatomic, assign) AccountHomeViewController *accountHomeViewController;
 
 - (IBAction)refreshButtonPressed:(id)sender;
+- (IBAction)homeButtonPressed:(id)sender;
 
 @end
index 0af4c37..bd1fe32 100644 (file)
@@ -43,7 +43,6 @@
 #import "OpenStackAccount.h"
 #import "AccountManager.h"
 #import "ActivityIndicatorView.h"
-#import "SBJSON.h"
 #import "AccountHomeViewController.h"
 #import "APICallback.h"
 
@@ -56,7 +55,6 @@
 - (void)viewDidLoad {
     [super viewDidLoad];
     self.navigationItem.title = @"Sharing Accounts";
-    [self addHomeButton];
     sharingAccounts = [[NSMutableArray alloc] init];
 }
 
 
 #pragma mark - Button Handlers
 
-- (IBAction)homeButtonPressed:(id)sender {
-    [self.navigationController popToViewController:accountHomeViewController animated:YES];
-}
-
-- (IBAction)refreshButtonPressed:(id)sender {
+- (void)refreshButtonPressed:(id)sender {
     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading..."
                                                                                                    andAddToView:self.view];
     [[self.account.manager getSharingAccounts]
      success:^(OpenStackRequest *request) {
-         SBJSON *parser = [[SBJSON alloc] init];
-         NSArray *jsonObjects = [parser objectWithString:[request responseString]];
-         
          [sharingAccounts removeAllObjects];
-         for (int i = 0; i < [jsonObjects count]; i++) {
-             NSDictionary *dict = [jsonObjects objectAtIndex:i];
+         for (NSDictionary *dict in [request sharingAccounts]) {
              [sharingAccounts addObject:[dict objectForKey:@"name"]];
          }
-         [parser release];
-         contentsLoaded = YES;
-         
-         [activityIndicatorView removeFromSuperview]; 
-         [self.tableView reloadData];
+         [[self.account.manager userCatalogForDisplaynames:nil
+                                                     UUIDs:sharingAccounts]
+          success:^(OpenStackRequest *request) {
+              contentsLoaded = YES;
+              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+              [self.tableView reloadData];
+          }
+          failure:^(OpenStackRequest *request) {
+              contentsLoaded = YES;
+              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+              [self.tableView reloadData];
+          }];
      }
      failure:^(OpenStackRequest *request) {
          contentsLoaded = NO;
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          if (request.responseStatusCode != 0) {
              [self alert:@"There was a problem loading sharing accounts." request:request addAccountSettingsButton:YES];
          }
      }];
 }
 
+- (void)homeButtonPressed:(id)sender {
+    [self.navigationController popToViewController:accountHomeViewController animated:YES];
+}
+
 #pragma mark - UITableViewDataSource
 
 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
         }
-        cell.textLabel.text = [sharingAccounts objectAtIndex:indexPath.row];
+        cell.textLabel.text = [account displaynameForUUID:[sharingAccounts objectAtIndex:indexPath.row] safe:YES];
         cell.imageView.image = [UIImage imageNamed:@"myShared.png"];
         
         return cell;
index 5303172..9e0ed14 100644 (file)
                                                                <reference key="IBUIToolbar" ref="606510169"/>
                                                                <int key="IBUISystemItemIdentifier">5</int>
                                                        </object>
+                                                       <object class="IBUIBarButtonItem" id="1072651937">
+                                                               <object class="NSCustomResource" key="IBUIImage">
+                                                                       <string key="NSClassName">NSImage</string>
+                                                                       <string key="NSResourceName">HomeFolderIcon.png</string>
+                                                               </object>
+                                                               <string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
+                                                               <float key="IBUIWidth">33</float>
+                                                               <reference key="IBUIToolbar" ref="606510169"/>
+                                                       </object>
                                                </array>
                                        </object>
                                </array>
                                        </object>
                                        <int key="connectionID">12</int>
                                </object>
+                               <object class="IBConnectionRecord">
+                                       <object class="IBCocoaTouchEventConnection" key="connection">
+                                               <string key="label">homeButtonPressed:</string>
+                                               <reference key="source" ref="1072651937"/>
+                                               <reference key="destination" ref="372490531"/>
+                                       </object>
+                                       <int key="connectionID">15</int>
+                               </object>
                        </array>
                        <object class="IBMutableOrderedSet" key="objectRecords">
                                <array key="orderedObjects">
                                                <array class="NSMutableArray" key="children">
                                                        <reference ref="856980934"/>
                                                        <reference ref="458687891"/>
+                                                       <reference ref="1072651937"/>
                                                </array>
                                                <reference key="parent" ref="191373211"/>
                                        </object>
                                                <reference key="object" ref="458687891"/>
                                                <reference key="parent" ref="606510169"/>
                                        </object>
+                                       <object class="IBObjectRecord">
+                                               <int key="objectID">14</int>
+                                               <reference key="object" ref="1072651937"/>
+                                               <reference key="parent" ref="606510169"/>
+                                       </object>
                                </array>
                        </object>
                        <dictionary class="NSMutableDictionary" key="flattenedProperties">
                                <string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                <string key="1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                <string key="13.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
+                               <string key="14.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                <string key="4.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                <string key="5.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                                <string key="6.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
                        <nil key="activeLocalization"/>
                        <dictionary class="NSMutableDictionary" key="localizations"/>
                        <nil key="sourceID"/>
-                       <int key="maxID">13</int>
+                       <int key="maxID">15</int>
                </object>
                <object class="IBClassDescriber" key="IBDocument.Classes">
                        <array class="NSMutableArray" key="referencedPartialClassDescriptions">
                                <object class="IBPartialClassDescription">
                                        <string key="className">SharingAccountsViewController</string>
                                        <string key="superclassName">OpenStackViewController</string>
-                                       <object class="NSMutableDictionary" key="actions">
-                                               <string key="NS.key.0">refreshButtonPressed:</string>
-                                               <string key="NS.object.0">id</string>
-                                       </object>
-                                       <object class="NSMutableDictionary" key="actionInfosByName">
-                                               <string key="NS.key.0">refreshButtonPressed:</string>
-                                               <object class="IBActionInfo" key="NS.object.0">
+                                       <dictionary class="NSMutableDictionary" key="actions">
+                                               <string key="homeButtonPressed:">id</string>
+                                               <string key="refreshButtonPressed:">id</string>
+                                       </dictionary>
+                                       <dictionary class="NSMutableDictionary" key="actionInfosByName">
+                                               <object class="IBActionInfo" key="homeButtonPressed:">
+                                                       <string key="name">homeButtonPressed:</string>
+                                                       <string key="candidateClassName">id</string>
+                                               </object>
+                                               <object class="IBActionInfo" key="refreshButtonPressed:">
                                                        <string key="name">refreshButtonPressed:</string>
                                                        <string key="candidateClassName">id</string>
                                                </object>
-                                       </object>
+                                       </dictionary>
                                        <object class="NSMutableDictionary" key="outlets">
                                                <string key="NS.key.0">tableView</string>
                                                <string key="NS.object.0">UITableView</string>
                <string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
                <bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
                <int key="IBDocument.defaultPropertyAccessControl">3</int>
+               <object class="NSMutableDictionary" key="IBDocument.LastKnownImageSizes">
+                       <string key="NS.key.0">HomeFolderIcon.png</string>
+                       <string key="NS.object.0">{22, 22}</string>
+               </object>
                <string key="IBCocoaTouchPluginVersion">1929</string>
        </data>
 </archive>
index dcc7e0d..c2075ba 100755 (executable)
@@ -38,5 +38,7 @@
 - (void)setPropertiesfromResponseHeaders:(NSDictionary *)headers;
 - (NSString *)humanizedBytes;
 - (BOOL)isPlayableMedia;
+- (NSMutableDictionary *)permissions;
+- (void)setPermissions:(NSMutableDictionary *)permissions;
 
 @end
index 56bf303..a590fcb 100755 (executable)
@@ -86,7 +86,7 @@
     self.hash = [headers objectForKey:@"X-Object-Hash"];
     self.bytes = [[headers objectForKey:@"Content-Length"] intValue];
     self.contentType = [headers objectForKey:@"Content-Type"];
-    self.lastModified = [headers objectForKey:@"Last-Modified"];
+    self.lastModified = [ComputeModel dateFromRFC1123String:[headers objectForKey:@"Last-Modified"]];
     self.publicURI = [headers objectForKey:@"X-Object-Public"];
     self.sharing = [headers objectForKey:@"X-Object-Sharing"];
     
     return matches > 0;
 }
 
+- (NSMutableDictionary *)permissions {
+    NSMutableDictionary *permissions = [NSMutableDictionary dictionary];
+    if (self.sharing.length) {
+        for (NSString *sharingList in [self.sharing componentsSeparatedByString:@";"]) {
+            NSArray *components = [sharingList componentsSeparatedByString:@"="];
+            NSString *type = [components objectAtIndex:0];
+            if ([type hasPrefix:@" "]) {
+                type = [type substringFromIndex:1];
+            }
+            for (NSString *user in [[components objectAtIndex:1] componentsSeparatedByString:@","]) {
+                [permissions setObject:type forKey:user];
+            }
+        }
+    }
+    return permissions;
+}
+
+- (void)setPermissions:(NSMutableDictionary *)permissions {
+    NSMutableString *readSharingList = nil;
+    NSMutableString *writeSharingList = nil;
+    for (NSString *user in permissions) {
+        if ([[permissions objectForKey:user] isEqual:@"read"]) {
+            if (!readSharingList) {
+                readSharingList = [NSMutableString stringWithFormat:@"read=%@", user];
+            } else {
+                [readSharingList appendFormat:@",%@", user];
+            }
+        } else if ([[permissions objectForKey:user] isEqual:@"write"]) {
+            if (!writeSharingList) {
+                writeSharingList = [NSMutableString stringWithFormat:@"write=%@", user];
+            } else {
+                [writeSharingList appendFormat:@",%@", user];
+            }
+        }
+    }
+    if (readSharingList) {
+        if (writeSharingList) {
+            self.sharing = [NSString stringWithFormat:@"%@;%@", readSharingList, writeSharingList];
+        } else {
+            self.sharing = readSharingList;
+        }
+    } else if (writeSharingList) {
+        self.sharing = writeSharingList;
+    } else {
+        self.sharing = @"";
+    }
+}
+
 @end
index 3336f1c..69f7a7f 100755 (executable)
@@ -50,7 +50,8 @@ typedef NSInteger StorageObjectAction;
 @property (nonatomic, retain) Folder *folder;
 @property (nonatomic, retain) StorageObject *object;
 @property (nonatomic, retain) FolderViewController *folderViewController;
-@property (nonatomic, retain) NSString *oldPubicURI;
+@property (nonatomic, retain) NSString *oldPublicURI;
+@property (nonatomic, retain) NSMutableDictionary *permissions;
 @property (nonatomic, retain) UIDocumentInteractionController *documentInteractionController;
 @property (nonatomic, assign) BOOL objectIsReadOnly;
 @property (nonatomic, retain) NSString *versionID;
index 9c5b9b7..2fe3e14 100755 (executable)
@@ -76,7 +76,7 @@ Delete Object
 @implementation StorageObjectViewController
 
 @synthesize account, container, folder, object, folderViewController;
-@synthesize oldPubicURI, documentInteractionController, objectIsReadOnly, versionID;
+@synthesize oldPublicURI, permissions, documentInteractionController, objectIsReadOnly, versionID;
 
 #pragma mark - View lifecycle
 
@@ -90,25 +90,11 @@ Delete Object
     objectIsPublicSwitch = [[UISwitch alloc] init];
     [objectIsPublicSwitch addTarget:self action:@selector(objectIsPublicSwitchChanged:) forControlEvents:UIControlEventValueChanged];
     
-    permissions = [[NSMutableDictionary alloc] init];
-    if (object.sharing.length > 0) {
-        NSArray *sharingArray = [object.sharing componentsSeparatedByString:@";"];
-        for (NSString *typeSpecificPermissions in sharingArray) { 
-            NSArray *array=[typeSpecificPermissions componentsSeparatedByString:@"="];
-            NSString *permissionsType = [array objectAtIndex:0];
-            if ([permissionsType hasPrefix:@" "])
-                permissionsType = [permissionsType substringFromIndex:1];
-                            
-            NSArray *users = [[array objectAtIndex:1] componentsSeparatedByString:@","];
-            for (NSString *user in users) {
-                [permissions setObject:permissionsType forKey:user];
-            }
-        }
-    }
+    self.permissions = [object permissions];
     objectIsReadOnly = NO;
     if (account.sharingAccount) { 
-        if ([permissions count] > 0) {
-            objectIsReadOnly = [[permissions objectForKey:[account username]] isEqualToString:@"read"];
+        if (permissions.count) {
+            objectIsReadOnly = ![[permissions objectForKey:account.username] isEqualToString:@"write"];
         }
         objectIsPublicSwitch.enabled = NO;
     }
@@ -169,8 +155,11 @@ Delete Object
 }
 
 - (void)viewDidAppear:(BOOL)animated {
-    if (object.metadata == nil) {
+    [super viewDidAppear:animated];
+    if (!object.metadata) {
         [self reloadMetadataSection];
+    } else {
+        [self updatePermissionsUserCatalog];
     }
     if (fileDownloading) {
         [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:kActions]
@@ -191,8 +180,9 @@ Delete Object
     [downloadProgressView release];
     [cdnURLActionSheet release];
     [folderViewController release];
-    [objectIsPublicSwitch release];
+    [oldPublicURI release];
     [permissions release];
+    [objectIsPublicSwitch release];
     [documentInteractionController release];
     [versionID release];
     [super dealloc];
@@ -202,7 +192,7 @@ Delete Object
 
 - (void)objectIsPublicSwitchChanged:(id)sender {
     NSString *activityMessage = [NSString stringWithFormat:@"Enabling public link.."];
-    self.oldPubicURI = object.publicURI;
+    self.oldPublicURI = object.publicURI;
     
     if (objectIsPublic) {
         activityMessage = [NSString stringWithFormat:@"Disabling public link.."];
@@ -220,27 +210,27 @@ Delete Object
          if (objectIsPublic) {
              [[self.account.manager getObjectInfo:container object:object version:versionID]
               success:^(OpenStackRequest *request) {
-                  [activityIndicatorView removeFromSuperview];
+                  [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                   object.publicURI = [request.responseHeaders objectForKey:@"X-Object-Public"];
                   NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:publicLinkSection];
                   [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
                                         withRowAnimation:UITableViewRowAnimationBottom];
               }
               failure:^(OpenStackRequest *request) {
-                  [activityIndicatorView removeFromSuperview];
+                  [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
                   [self alert:@"There was a problem retrieving the public link from the server." request:request];
               }];
          } else {
-             [activityIndicatorView removeFromSuperview];
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
              [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
                                    withRowAnimation:UITableViewRowAnimationTop];
          }
      }
      failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          objectIsPublic = !objectIsPublic;
          objectIsPublicSwitch.on = !objectIsPublicSwitch.on;
-         object.publicURI = oldPubicURI;
+         object.publicURI = oldPublicURI;
          [self alert:@"There was a problem enabling the public link." request:request];
      }];
 }
@@ -298,6 +288,40 @@ Delete Object
     }
 }
 
+- (void)updatePermissionsUserCatalog {
+    NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:permissions.count];
+    for (NSString *user in permissions) {
+        NSRange rangeOfColumn = [user rangeOfString:@":"];
+        NSString *UUID = (rangeOfColumn.location == NSNotFound) ? user : [user substringToIndex:rangeOfColumn.location];
+        if (![UUID isEqualToString:@"*"]) {
+            [UUIDs addObject:UUID];
+        }
+    }
+    if (UUIDs.count) {
+        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading permissions..."
+                                                                                                       andAddToView:self.view];
+        [[self.account.manager userCatalogForDisplaynames:nil UUIDs:UUIDs]
+         success:^(OpenStackRequest *request) {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:permissionsSection] withRowAnimation:UITableViewRowAnimationNone];
+         }
+         failure:^(OpenStackRequest *request) {
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self alert:@"Failed to translate sharing UUIDs." request:request];
+         }];
+    }
+}
+
+- (void)delete {
+    if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
+        folderViewController.deletedObject = object;
+        [self.navigationController popViewControllerAnimated:YES];
+    } else {
+        [folderViewController deleteAnimatedObject:object];
+        [folderViewController setDetailViewController];
+    }
+}
+
 #pragma mark - Actions
 
 - (void)reloadMetadataSection {
@@ -305,7 +329,7 @@ Delete Object
                                                                                                    andAddToView:self.view];
     [[self.account.manager getObjectInfo:container object:object version:versionID]
      success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          object.metadata = [NSMutableDictionary dictionary];
          for (NSString *header in request.responseHeaders) {
              NSString *metadataKey;
@@ -316,11 +340,11 @@ Delete Object
                  [object.metadata setObject:metadataValue forKey:metadataKey];
              }
          }
-         NSIndexSet *metadataSections = [NSIndexSet indexSetWithIndex:kMetadata];
-         [self.tableView reloadSections:metadataSections withRowAnimation:UITableViewRowAnimationFade];
+         [self.tableView reloadData];
+         [self updatePermissionsUserCatalog];
      }
      failure:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [self alert:@"There was a problem retrieving the object's metadata." request:request];
      }];
 }
@@ -352,7 +376,6 @@ Delete Object
                  }
              }
              [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
-             [self.folderViewController reloadData];
          }
      }
      failure:^(OpenStackRequest *request) {
@@ -394,15 +417,15 @@ Delete Object
         if (objectIsReadOnly) {
             return [object.metadata count];
         } else {
-            return 1 + [object.metadata count];
+            return ([object.metadata count] + 1);
         }
     } else if (section == publicLinkSection) {
-        return objectIsPublic ? 2 : 1;
+        return (objectIsPublic ? 2 : 1);
     } else if (section == permissionsSection) {
         if (account.sharingAccount || objectIsReadOnly) {
-            return [permissions count];
+            return permissions.count;
         } else {
-            return 1 + [permissions count];
+            return (permissions.count + 1);
         }
     } else {
         return 1;
@@ -424,7 +447,7 @@ Delete Object
                                             CGSizeMake(221.0, 9000.0))
                              lineBreakMode:UILineBreakModeCharacterWrap].height;
     } else if ((indexPath.section == publicLinkSection) && (indexPath.row == 1)) {
-        NSURL *publicLinkURL = [account.pithosPublicLinkURLPrefix URLByAppendingPathComponent:
+        NSURL *publicLinkURL = [account.provider.publicLinkURLPrefix URLByAppendingPathComponent:
                                 [NSString encodeToPercentEscape:[self.object.publicURI substringFromIndex:1]
                                              charactersToEncode:@"!*'();:@&=+$,?%#[]"]];
         result = 30.0 + [[publicLinkURL description] sizeWithFont:[UIFont systemFontOfSize:18.0]
@@ -449,7 +472,7 @@ Delete Object
         cell.textView.backgroundColor = [UIColor clearColor];
         cell.textView.font = [UIFont systemFontOfSize:15.0];
         cell.textView.dataDetectorTypes = UIDataDetectorTypeLink;
-        cell.textView.text = [[account.pithosPublicLinkURLPrefix URLByAppendingPathComponent:
+        cell.textView.text = [[account.provider.publicLinkURLPrefix URLByAppendingPathComponent:
                                [NSString encodeToPercentEscape:[self.object.publicURI substringFromIndex:1]
                                             charactersToEncode:@"!*'();:@&=+$,?%#[]"]] description];
         cell.selectionStyle = UITableViewCellSelectionStyleNone;
@@ -553,27 +576,29 @@ Delete Object
             cell.accessoryType = UITableViewCellAccessoryNone;
             cell.selectionStyle = UITableViewCellSelectionStyleNone;
             cell.userInteractionEnabled = NO;
-        }
-        else {
+        } else {
             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
         }
         cell.accessoryView = nil;
         
-        if (indexPath.row == [permissions count]) {
+        if (indexPath.row == permissions.count) {
             cell.textLabel.text = @"Share";
             cell.detailTextLabel.text = @""; 
         } else {
-            NSString *user = [[permissions allKeys] objectAtIndex:indexPath.row];
-            cell.textLabel.text = user;
-            NSString *accessType;
-            if ([[permissions objectForKey:user] isEqualToString:@"write"])
-                accessType = @"Read/Write";
-            else
-                accessType = @"Read Only";
+            NSString *user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
+            NSRange rangeOfColumn = [user rangeOfString:@":"];
+            NSString *UUID = (rangeOfColumn.location == NSNotFound) ? user : [user substringToIndex:rangeOfColumn.location];
+            NSString *group = ((rangeOfColumn.location == NSNotFound) || (rangeOfColumn.location == user.length - 1)) ? nil : [user substringFromIndex:(rangeOfColumn.location + 1)];
+            NSMutableString *displayname = [NSMutableString stringWithString:[account displaynameForUUID:UUID safe:YES]];
+            if (group) {
+                [displayname appendFormat:@":%@", group];
+            }
+            cell.textLabel.text = displayname;
+            
             cell.detailTextLabel.numberOfLines = 1;
             cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
-            cell.detailTextLabel.text = accessType;
+            cell.detailTextLabel.text = ([[permissions objectForKey:user] isEqualToString:@"write"] ? @"Read/Write" : @"Read Only");
         }
     } else if (indexPath.section == kActions) {
         if (indexPath.row == 0) {
@@ -656,28 +681,28 @@ Delete Object
         EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
         NSString *user;
         
-        if (indexPath.row == [permissions count]) {
+        if (indexPath.row == permissions.count) {
             user = @"";
             vc.removePermissionsEnabled = NO;
-            vc.navigationItem.title = @"Share";
+            vc.navigationItem.title = @"Add Permission";
         } else {
-            user = [[permissions allKeys] objectAtIndex:indexPath.row];
+            user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
             NSString *userPermissions = [permissions objectForKey:user];
-            if ([userPermissions rangeOfString:@"read"].location != NSNotFound)
+            if ([userPermissions rangeOfString:@"read"].location != NSNotFound) {
                 vc.readPermissionSelected = YES;
-            else
+            } else {
                 vc.readPermissionSelected = NO;
-            
-            if ([userPermissions rangeOfString:@"write"].location != NSNotFound)
+            }
+            if ([userPermissions rangeOfString:@"write"].location != NSNotFound) {
                 vc.writePermissionSelected = YES;
-            else
+            } else {
                 vc.writePermissionSelected = NO;
-            
+            }
             vc.removePermissionsEnabled = YES;
-            vc.navigationItem.title = @"Edit Sharing";
+            vc.navigationItem.title = @"Edit Permission";
         }
         
-        vc.user = user;
+        vc.permissionUser = user;
         vc.permissions = permissions;
         vc.account = account;
         vc.container = container;
@@ -760,36 +785,21 @@ Delete Object
 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
     if (buttonIndex == 0) {
         // delete the file and pop out
-        self.folderViewController.refreshButton.enabled = NO;
         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Deleting file..."
                                                                                                        andAddToView:self.view];
         [[self.account.manager deleteObject:self.container object:self.object] 
          success:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
-                 [self.navigationController popViewControllerAnimated:YES];
-                 if (account.shared)
-                     self.folderViewController.needsRefreshing = YES;
-             } else if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
-                 self.folderViewController.selectedObjectViewController = nil;
-                 if (!account.shared)
-                     [self.folderViewController setDetailViewController];
-                 else 
-                    [self.folderViewController refreshButtonPressed:nil];
-//                 self.folderViewController.refreshButton.enabled = YES;
-             }
-             if (self.folder.objectsAndFoldersCount == 1) {
-                 [self.folder removeObject:self.object];
-                 self.folderViewController.folder = self.folderViewController.folder;
-             } else {
-                 [self.folderViewController deleteAnimatedObject:self.object];
-             }
-             self.folderViewController.refreshButton.enabled = YES;
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             [self delete];
          }
          failure:^(OpenStackRequest *request) {
-             [activityIndicatorView removeFromSuperview];
-             [self alert:@"There was a problem deleting this file." request:request];
-             self.folderViewController.refreshButton.enabled = YES;
+             [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+             // 404 Not Found means it's not there, so we can show the user that it's deleted
+             if (request.responseStatusCode == 404) {
+                 [self delete];
+             } else {
+                  [self alert:@"There was a problem deleting this file." request:request];
+             }
          }];
     }
     NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:deleteSection];
index d8c55d8..1231423 100755 (executable)
@@ -13,8 +13,6 @@
     Container *container;
     Folder *folder;
     FolderViewController *folderViewController;
-    id successObserver;
-    id failureObserver;
     NSData *data;
     UITextField *nameTextField;
     UILabel *formatLabel;    
index 8e0f76d..e8d7da0 100755 (executable)
                                                                                                    andAddToView:self.view];
     [[self.account.manager writeObject:self.container object:object downloadProgressDelegate:activityIndicatorView.progressView]
      success:^(OpenStackRequest *request) {
-         [activityIndicatorView removeFromSuperview];
          object.data = nil;
          object.sharing = folder.sharing;
          object.lastModified = [ComputeModel dateFromRFC1123String:[request.responseHeaders objectForKey:@"Date"]];
-         
-         BOOL currentFolderIsRoot = NO;
-         if ([folderViewController.folder isEqual:container.rootFolder]) {
-             currentFolderIsRoot = YES;
-         }
+         BOOL currentFolderIsRoot = (folderViewController.folder == container.rootFolder);
          [folder addObject:object];
          folderViewController.folder = folderViewController.folder;
          if (currentFolderIsRoot) {
              container.count += 1;
              container.rootFolder = folder;
-             [self.account.containers setObject:container forKey:container.name];
          }
-         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
-             [folderViewController setDetailViewController];
          allowOverwrite = NO;
-         [self dismissModalViewControllerAnimated:YES];
+         [[self.account.manager getObjectInfo:self.container object:object]
+          success:^(OpenStackRequest *request) {
+              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+              object.hash = [request.responseHeaders objectForKey:@"X-Object-Hash"];
+              [self dismissModalViewControllerAnimated:YES];
+          }
+          failure:^(OpenStackRequest *request) {
+              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
+              [self dismissModalViewControllerAnimated:YES];
+          }];
      }
      failure:^(OpenStackRequest *request) {
          allowOverwrite = NO;
-         [activityIndicatorView removeFromSuperview];
+         [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
          [self alert:@"There was a problem uploading the file." request:request];
      }];    
 }
index 3eba9e2..830f54e 100755 (executable)
@@ -6,8 +6,6 @@
        <string>English</string>
        <key>CFBundleDisplayName</key>
        <string>${PRODUCT_NAME}</string>
-       <key>CFBundleDocumentTypes</key>
-       <array/>
        <key>CFBundleExecutable</key>
        <string>${EXECUTABLE_NAME}</string>
        <key>CFBundleIconFile</key>
@@ -43,7 +41,7 @@
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
-       <string>1.5.1</string>
+       <string>1.6.2</string>
        <key>CFBundleSignature</key>
        <string>????</string>
        <key>CFBundleURLTypes</key>
@@ -58,7 +56,7 @@
                </dict>
        </array>
        <key>CFBundleVersion</key>
-       <string>20130122.0</string>
+       <string>20130209.0</string>
        <key>LSRequiresIPhoneOS</key>
        <true/>
        <key>NSMainNibFile</key>
@@ -80,9 +78,5 @@
                <string>UIInterfaceOrientationLandscapeLeft</string>
                <string>UIInterfaceOrientationLandscapeRight</string>
        </array>
-       <key>UTExportedTypeDeclarations</key>
-       <array/>
-       <key>UTImportedTypeDeclarations</key>
-       <array/>
 </dict>
 </plist>
index 98c927e..6b5dc23 100755 (executable)
@@ -7,7 +7,6 @@
        objects = {
 
 /* Begin PBXBuildFile section */
-               1D18292C16AFE84A00C9FCCD /* OpenStack-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 8D1107310486CEB800E47090 /* OpenStack-Info.plist */; };
                1D3623260D0F684500981E51 /* OpenStackAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 1D3623250D0F684500981E51 /* OpenStackAppDelegate.m */; };
                1D60589B0D05DD56006BFB54 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; };
                24332D531472839300063C8B /* myShared.png in Resources */ = {isa = PBXBuildFile; fileRef = 24332D521472839300063C8B /* myShared.png */; };
                2787C13E12F0D26A009EAD7C /* text-file.txt in Resources */ = {isa = PBXBuildFile; fileRef = 2787C13D12F0D26A009EAD7C /* text-file.txt */; };
                2788941B12C28C66006448E2 /* ContainerDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 2788941812C28C66006448E2 /* ContainerDetailViewController.m */; };
                2788941C12C28C66006448E2 /* ContainerDetailViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 2788941912C28C66006448E2 /* ContainerDetailViewController.xib */; };
-               2788965612C52E5F006448E2 /* GetContainersRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 2788965412C52E5F006448E2 /* GetContainersRequest.m */; };
                278896B312C53956006448E2 /* GetObjectsRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 278896B112C53956006448E2 /* GetObjectsRequest.m */; };
                278906E012BEDAB5007112B6 /* StorageObjectViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 278906DD12BEDAB5007112B6 /* StorageObjectViewController.xib */; };
                278907A212BEF72C007112B6 /* StorageObjectViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 278907A112BEF72C007112B6 /* StorageObjectViewController.m */; };
                2788941712C28C66006448E2 /* ContainerDetailViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ContainerDetailViewController.h; sourceTree = "<group>"; };
                2788941812C28C66006448E2 /* ContainerDetailViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ContainerDetailViewController.m; sourceTree = "<group>"; };
                2788941912C28C66006448E2 /* ContainerDetailViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ContainerDetailViewController.xib; sourceTree = "<group>"; };
-               2788965312C52E5F006448E2 /* GetContainersRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GetContainersRequest.h; sourceTree = "<group>"; };
-               2788965412C52E5F006448E2 /* GetContainersRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GetContainersRequest.m; sourceTree = "<group>"; };
                278896B012C53956006448E2 /* GetObjectsRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GetObjectsRequest.h; sourceTree = "<group>"; };
                278896B112C53956006448E2 /* GetObjectsRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GetObjectsRequest.m; sourceTree = "<group>"; };
                278906DB12BEDAB5007112B6 /* StorageObjectViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StorageObjectViewController.h; sourceTree = "<group>"; };
                2788958612C476A3006448E2 /* Object Storage */ = {
                        isa = PBXGroup;
                        children = (
-                               2788965312C52E5F006448E2 /* GetContainersRequest.h */,
-                               2788965412C52E5F006448E2 /* GetContainersRequest.m */,
                                278896B012C53956006448E2 /* GetObjectsRequest.h */,
                                278896B112C53956006448E2 /* GetObjectsRequest.m */,
                        );
                                6142B27D1523602300A1BFAD /* pithos_icon_iPad@2x.png in Resources */,
                                6152AEEF166519F200A3EA6B /* Default-568h@2x.png in Resources */,
                                61BC2A2D166BDAE5002DF74D /* FolderViewController-iPad.xib in Resources */,
-                               1D18292C16AFE84A00C9FCCD /* OpenStack-Info.plist in Resources */,
                        );
                        runOnlyForDeploymentPostprocessing = 0;
                };
                                277B89B912B97D6D006483B0 /* FolderViewController.m in Sources */,
                                278907A212BEF72C007112B6 /* StorageObjectViewController.m in Sources */,
                                2788941B12C28C66006448E2 /* ContainerDetailViewController.m in Sources */,
-                               2788965612C52E5F006448E2 /* GetContainersRequest.m in Sources */,
                                278896B312C53956006448E2 /* GetObjectsRequest.m in Sources */,
                                2734687112D3AB9500341268 /* AddObjectViewController.m in Sources */,
                                273468C512D3B0E800341268 /* AddFolderViewController.m in Sources */,