Revision 3e581f16

b/Classes/AccountGroupsViewController.h
45 45

  
46 46
@property (nonatomic, retain) OpenStackAccount *account;
47 47
@property (nonatomic, retain) NSMutableDictionary *groups;
48
@property (nonatomic, retain) NSMutableDictionary *metadata;
48 49

  
49 50
@end
b/Classes/AccountGroupsViewController.m
47 47

  
48 48
@implementation AccountGroupsViewController
49 49

  
50
@synthesize account, groups;
50
@synthesize account, groups, metadata;
51 51

  
52 52
#pragma mark - View lifecycle
53 53

  
......
58 58
- (void)viewWillAppear:(BOOL)animated {
59 59
    [super viewWillAppear:animated];
60 60
    self.navigationItem.title = @"Groups";
61
    groups = [[NSMutableDictionary alloc] init];
62
    metadata = [[NSMutableDictionary alloc] init];
61
}
63 62

  
64
    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading..."
63
- (void)viewDidAppear:(BOOL)animated {
64
    [super viewDidAppear:animated];
65
    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading groups..."
65 66
                                                                                                   andAddToView:self.view];
66 67
    [[self.account.manager getStorageAccountInfo]
67 68
     success:^(OpenStackRequest *request) {
68
         [activityIndicatorView removeFromSuperview];
69
         for (NSString *key in request.responseHeaders) {
70
             if ([key hasPrefix:@"X-Account-Group-"]) {
71
                 NSString *groupUsers = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:key]];
72
                 NSString *groupName = [NSString decodeFromPercentEscape:[key substringFromIndex:16]];
73
                 [groups setObject:groupUsers forKey:groupName];
69
         self.groups = [NSMutableDictionary dictionary];
70
         self.metadata = [NSMutableDictionary dictionary];
71
     
72
         for (NSString *headerName in request.responseHeaders) {
73
             if ([headerName hasPrefix:@"X-Account-Group-"]) {
74
                 [groups setObject:[[NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:headerName]] componentsSeparatedByString:@","]
75
                            forKey:[NSString decodeFromPercentEscape:[headerName substringFromIndex:16]]];
76
              } else if ([headerName hasPrefix:@"X-Account-Meta-"]) {
77
                 [metadata setObject:[NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:headerName]]
78
                              forKey:[NSString decodeFromPercentEscape:[headerName substringFromIndex:15]]];
74 79
             }
75 80
         }
76 81
         
77
         for (NSString *header in request.responseHeaders) {
78
             if ([header hasPrefix:@"X-Account-Meta-"])  {
79
                 NSString *metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:15]];
80
                 NSString *metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
81
                 [metadata setObject:metadataValue forKey:metadataKey];
82
             }
82
         NSMutableArray *UUIDs = [NSMutableArray array];
83
         for (NSString *groupName in groups) {
84
             [UUIDs addObjectsFromArray:[groups objectForKey:groupName]];
85
         }
86
         if (UUIDs.count) {
87
             [[self.account.manager userCatalogForDisplaynames:nil UUIDs:UUIDs]
88
              success:^(OpenStackRequest *request) {
89
                  [activityIndicatorView removeFromSuperview];
90
                  [self.tableView reloadData];
91
              }
92
              failure:^(OpenStackRequest *request) {
93
                  [activityIndicatorView removeFromSuperview];
94
                  [self.tableView reloadData];
95
                  [self alert:@"Failed to translate group UUIDs." request:request];
96
              }];
97
         } else {
98
             [activityIndicatorView removeFromSuperview];
99
             [self.tableView reloadData];
83 100
         }
84
         
85
         [self.tableView reloadData];
86 101
     }
87 102
     failure:^(OpenStackRequest *request) {
88 103
         [activityIndicatorView removeFromSuperview];
89
         [self alert:@"Error" message:@"Failed to get account information"];
104
         [self alert:@"Failed to get account information." request:request];
90 105
     }];
91 106
}
92 107

  
108
#pragma mark - Internal
109

  
110
- (NSMutableArray *)groupUsersForGroupName:(NSString *)groupName {
111
    NSArray *groupUUIDs = [groups objectForKey:groupName];
112
    NSMutableArray *groupUsers = [NSMutableArray arrayWithCapacity:groupUUIDs.count];
113
    for (NSString *UUID in groupUUIDs) {
114
        [groupUsers addObject:[self.account displaynameForUUID:UUID safe:YES]];
115
    }
116
    return groupUsers;
117
}
118

  
119
- (NSMutableString *)groupUsersStringForGroupName:(NSString *)groupName {
120
    NSMutableArray *groupUsers = [self groupUsersForGroupName:groupName];
121
    NSMutableString *groupUsersString = [NSMutableString string];
122
    for (NSUInteger index = 0; index < groupUsers.count; index++) {
123
        if (index) {
124
            [groupUsersString appendString:@","];
125
        }
126
        [groupUsersString appendString:[groupUsers objectAtIndex:index]];
127
    }
128
    return groupUsersString;
129
}
130

  
93 131
#pragma mark - Memory management
94 132

  
95 133
- (void)dealloc {
......
102 140
#pragma mark - UITableViewDataSource
103 141

  
104 142
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
105
    return [groups count] + 1;
143
    return (groups.count + 1);
106 144
}
107 145

  
108 146
- (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
109 147
    CGFloat result;
110
    if ([groups count] > 0 && indexPath.row < [groups count]) {
111
        NSString *groupName = [[groups allKeys] objectAtIndex:indexPath.row];
112
        NSString *groupUsers = [groups objectForKey:groupName];
113
        result = 22.0 + [[NSString stringWithFormat:@"%@", groupUsers] sizeWithFont:[UIFont systemFontOfSize:18.0]
148
    if (indexPath.row < groups.count) {
149
        NSString *groupName = [[[groups allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
150
        result = 22.0 + [[self groupUsersStringForGroupName:groupName] sizeWithFont:[UIFont systemFontOfSize:18.0]
114 151
                                                                  constrainedToSize:(([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ?
115 152
                                                                                     CGSizeMake(596.0, 9000.0) :
116 153
                                                                                     CGSizeMake(280.0, 9000.0))
......
123 160

  
124 161
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
125 162
    static NSString *CellIdentifier = @"Cell";
126
    
127 163
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
128 164
    if (cell == nil) {
129 165
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
130 166
    }
131 167
    
132 168
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
133
    if (indexPath.row < [groups count]) {
134
        NSString *groupName = [[groups allKeys] objectAtIndex:indexPath.row];
169
    if (indexPath.row < groups.count) {
170
        NSString *groupName = [[[groups allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
135 171
        cell.textLabel.text = groupName;
136
        cell.detailTextLabel.text = [groups objectForKey:groupName];
172
        cell.detailTextLabel.text = [self groupUsersStringForGroupName:groupName];
137 173
        cell.detailTextLabel.numberOfLines = 0;
138 174
        cell.detailTextLabel.lineBreakMode = UILineBreakModeCharacterWrap;
139 175
    } else {
......
152 188
    vc.account = self.account;
153 189
    vc.metadata = metadata;
154 190
    vc.groups = groups;
155
    if (indexPath.row < [groups count]) {
156
        NSString *groupName = [[groups allKeys] objectAtIndex:indexPath.row];
157
        NSString *groupUsers = [groups objectForKey:groupName];
158
        
191
    if (indexPath.row < groups.count) {
192
        NSString *groupName = [[[groups allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
159 193
        vc.removeGroupEnabled = YES;
160 194
        vc.groupName = groupName;
161
        vc.groupUsers = groupUsers;
162 195
        vc.navigationItem.title = @"Edit Group";
163 196
    } else {
164 197
        vc.removeGroupEnabled = NO;
165 198
        vc.groupName = @"";
166
        vc.groupUsers = @"";
167 199
        vc.navigationItem.title = @"Add Group";
168 200
    }
169 201
    
b/Classes/EditAccountGroupsViewController.h
40 40
@interface EditAccountGroupsViewController : UITableViewController <UITextFieldDelegate> {
41 41
    OpenStackAccount *account;
42 42
    NSString *groupName;
43
    NSString *groupUsers;
44 43
    NSMutableDictionary *groups;
45 44
    NSMutableDictionary *metadata;
46 45
    
47 46
    BOOL removeGroupEnabled;
47
    NSArray *groupUUIDs;
48
    UITextField *groupNameTextField;
49
    NSMutableDictionary *groupUsersTextFields;
50
    NSInteger numberOfGroupUsersRows;
48 51
}
49 52

  
50 53
@property (nonatomic, retain) OpenStackAccount *account;
51 54
@property (nonatomic, retain) NSString *groupName;
52
@property (nonatomic, retain) NSString *groupUsers;
53 55
@property (nonatomic, retain) NSMutableDictionary *groups;
54 56
@property (nonatomic, retain) NSMutableDictionary *metadata;
55
@property (nonatomic) BOOL removeGroupEnabled;
57

  
58
@property (nonatomic, assign) BOOL removeGroupEnabled;
59
@property (nonatomic, retain) NSArray *groupUUIDs;
60
@property (nonatomic, retain) UITextField *groupNameTextField;
61
@property (nonatomic, retain) NSMutableDictionary *groupUsersTextFields;
62
@property (nonatomic, assign) NSInteger numberOfGroupUsersRows;
56 63

  
57 64
@end
b/Classes/EditAccountGroupsViewController.m
43 43
#import "OpenStackRequest.h"
44 44
#import "APICallback.h"
45 45

  
46
#define kGroupDetails 0 
47
#define kSave 1
48
#define kRemove 2
46
#define kGroupName 0 
47
#define kGroupUsers 1
48
#define kSave 2
49
#define kRemove 3
49 50

  
50 51
@implementation EditAccountGroupsViewController
51 52

  
52
@synthesize account;
53
@synthesize groupName, groupUsers, removeGroupEnabled;
54
@synthesize groups,metadata;
53
@synthesize account, groupName, groups, metadata;
54
@synthesize removeGroupEnabled, groupUUIDs, groupNameTextField, groupUsersTextFields, numberOfGroupUsersRows;
55 55

  
56 56
#pragma mark - View lifecycle
57 57

  
......
59 59
    return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
60 60
}
61 61

  
62
- (void)viewDidLoad {
63
    [super viewDidLoad];
64
    self.groupUUIDs = [groups objectForKey:groupName];
65
    if (!self.groupUUIDs) {
66
        self.groupUUIDs = [NSArray array];
67
        numberOfGroupUsersRows = 2;
68
        self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:1];
69
    } else {
70
        numberOfGroupUsersRows = groupUUIDs.count + 1;
71
        self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:groupUUIDs.count];
72
    }
73
}
74

  
62 75
#pragma mark - Memory management
63 76

  
64 77
- (void)dealloc {
65
    [groupName release];
66
    [groupUsers release];
67 78
    [account release];
68
    [metadata release];
79
    [groupName release];
69 80
    [groups release];
81
    [metadata release];
82
    [groupUUIDs release];
83
    [groupNameTextField release];
84
    [groupUsersTextFields release];
70 85
    [super dealloc];
71 86
}
72 87

  
73 88
#pragma mark - Internal
74 89

  
75
- (BOOL)userInputIsValid:(NSString *)input fieldName:(NSString *)fieldName {
76
    if (![input length] || ![[input stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length]) {
77
        [self alert:@"Invalid input" message:[NSString stringWithFormat:@"%@ field cannot be empty", fieldName]];
90
- (BOOL)groupNameIsValid:(NSString *)input {
91
    if (!input.length) {
92
        [self alert:@"Invalid input" message:@"Group name cannot be empty."];
78 93
        return NO;
79
    }
80
    NSCharacterSet *set = [NSCharacterSet characterSetWithCharactersInString:@"= "];
81
    if ([input rangeOfCharacterFromSet:set].location != NSNotFound) {
82
        [self alert:@"Invalid input" message:[NSString stringWithFormat:@"%@ field cannot contain '=' or whitespace characters", fieldName]];
94
    } else if ([input rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@"=,;"]].location != NSNotFound) {
95
        [self alert:@"Invalid input" message:@"Group name cannot contain '=', ',' or ';'."];
83 96
        return NO;
84 97
    }
85 98
    return YES;
86 99
}
87 100

  
101
- (NSMutableDictionary *)groupsInfoForGroups:(NSMutableDictionary *)groupsDictionary {
102
    NSMutableDictionary *groupsInfo = [NSMutableDictionary dictionaryWithCapacity:groupsDictionary.count];
103
    for (NSString *groupUser in groupsDictionary) {
104
        [groupsInfo setObject:[[groupsDictionary objectForKey:groupUser] componentsJoinedByString:@","]
105
                       forKey:groupUser];
106
    }
107
    return groupsInfo;
108
}
109

  
88 110
#pragma mark - UITableViewDataSource
89 111

  
90 112
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
91
    if (removeGroupEnabled)
92
        return 3;
93
    else
94
        return 2;
113
    return (removeGroupEnabled ? 4 : 3);
95 114
}
96 115

  
97 116
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
98
    if (section == kGroupDetails)
99
        return 2;
100
    else
101
        return 1;
117
    if (section == kGroupUsers) {
118
        return numberOfGroupUsersRows;
119
    }
120
    return 1;
121
}
122

  
123
- (UITextField *)textFieldForCell:(UITableViewCell *)cell {
124
    CGRect rect = CGRectInset(cell.contentView.bounds, 10.0, 10.0);
125
    UITextField *textField = [[[UITextField alloc] initWithFrame:rect] autorelease];
126
    textField.frame = rect;
127
    textField.clearButtonMode = UITextFieldViewModeWhileEditing;
128
    textField.backgroundColor = [UIColor clearColor];
129
    textField.opaque = YES;
130
    textField.autocorrectionType = UITextAutocorrectionTypeNo;
131
    textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
132
    textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
133
    return textField;
134
}
135

  
136
- (UITextField *)textFieldForCell:(UITableViewCell *)cell withIndexPath:(NSIndexPath *)indexPath {
137
    if (indexPath.section == kGroupName) {
138
        if (!groupNameTextField) {
139
            self.groupNameTextField = [self textFieldForCell:cell];
140
            groupNameTextField.placeholder = @"Group name";
141
            groupNameTextField.text = groupName;
142
            groupNameTextField.delegate = self;
143
        }
144
        return groupNameTextField;
145
    } else if (indexPath.section == kGroupUsers) {
146
        UITextField *groupUserTextField = [groupUsersTextFields objectForKey:indexPath];
147
        if (!groupUserTextField) {
148
            groupUserTextField = [self textFieldForCell:cell];
149
            groupUserTextField.placeholder = @"User";
150
            NSString *groupUser = (indexPath.row < groupUUIDs.count) ? [self.account displaynameForUUID:[groupUUIDs objectAtIndex:indexPath.row] safe:YES] : nil;
151
            groupUserTextField.text = (groupUser && groupUser.length) ? groupUser : nil;
152
            groupUserTextField.tag = indexPath.row;
153
            [groupUsersTextFields setObject:groupUserTextField forKey:indexPath];
154
            groupUserTextField.delegate = self;
155
        }
156
        return groupUserTextField;
157
    }
158
    return nil;
102 159
}
103 160

  
104 161
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
105
    static NSString *CellIdentifier = @"Cell";
162
    if ((indexPath.section == kGroupName) || ((indexPath.section == kGroupUsers) && (indexPath.row < numberOfGroupUsersRows - 1))) {
163
        static NSString *CellIdentifier = @"TextFieldCell";
164
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
165
        if (cell == nil) {
166
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
167
        }
168
        cell.textLabel.text = nil;
169
        cell.accessoryType = UITableViewCellAccessoryNone;
170
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
171
        for (UIView *view in cell.contentView.subviews) {
172
            [view removeFromSuperview];
173
        }
174
        UITextField *textField = [self textFieldForCell:cell withIndexPath:indexPath];
175
        textField.returnKeyType = (((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 2)) ?
176
                                   UIReturnKeyDefault : UIReturnKeyNext);
177
        [cell.contentView addSubview:textField];
178
        return cell;
179
    }
106 180
    
181
    static NSString *CellIdentifier = @"Cell";
107 182
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
108 183
    if (cell == nil) {
109 184
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
110 185
    }
111
    
112
    cell.textLabel.text = @"";
113 186
    cell.accessoryType = UITableViewCellAccessoryNone;
114
    if (indexPath.section == kGroupDetails) {
115
        UITextField *textField = nil;
116
        for (id subView in cell.contentView.subviews) {
117
            if ([subView isKindOfClass:[UITextField class]]) {
118
                textField = (UITextField *)subView;
119
            }
120
        }
121
        
122
        if (textField == nil) {
123
            CGRect bounds = [cell.contentView bounds];
124
            CGRect rect = CGRectInset(bounds, 10.0, 10.0);                        
125
            textField = [[UITextField alloc] initWithFrame:rect];
126
            [textField setFrame:rect];
127
        }
128
        
129
        [textField setClearButtonMode:UITextFieldViewModeWhileEditing];
130
        [textField setBackgroundColor:[UIColor clearColor]];
131
        [textField setOpaque:YES];
132
        [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
133
        [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
134
        [textField setDelegate:self];
135
        textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
136 187

  
137
        if (indexPath.row == 0) {
138
            textField.returnKeyType = UIReturnKeyNext;
139
            textField.placeholder = @"Group name";
140
            textField.text = self.groupName;
141
            textField.tag = 0;
142
        } else if (indexPath.row == 1) {
143
            textField.returnKeyType = UIReturnKeyDefault;
144
            textField.placeholder = @"Group users";
145
            textField.text = self.groupUsers;
146
            textField.tag = 1;
147
        }
148
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
149
        [cell.contentView addSubview:textField];
188
    if ((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 1)) {
189
        cell.textLabel.text = @"Add User";
190
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
150 191
    } else if (indexPath.section == kSave) {
151
        cell.textLabel.text = @"Save";
192
        cell.textLabel.text = @"Save Group";
193
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
152 194
    } else if (indexPath.section == kRemove) {
153
        cell.textLabel.text = @"Remove";
195
        cell.textLabel.text = @"Remove Group";
196
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
154 197
    }
155 198
    
156 199
    return cell;
......
159 202
#pragma mark - UITableViewDelegate
160 203

  
161 204
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
162
    NSIndexPath *keyCellIndexPath;
163
    NSIndexPath *valueCellIndexPath;
164
    UITableViewCell *cell;
165

  
166
    if (indexPath.section != kGroupDetails)
205
    if (((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 1)) ||
206
        (indexPath.section == kSave) || (indexPath.section == kRemove))
167 207
        [self.view endEditing:YES];
168
    
169
    if (indexPath.section == kSave) {
170
        keyCellIndexPath = [NSIndexPath indexPathForRow:0 inSection:kGroupDetails];
171
        cell = [self.tableView cellForRowAtIndexPath:keyCellIndexPath];
172
        UITextField *textField = [[cell.contentView subviews] objectAtIndex:0];
173
        NSString *newGroupName = textField.text;
174
        
175
        if (![self userInputIsValid:newGroupName fieldName:@"Group name"]) {
176
            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
177
            return;
178
        }
179 208

  
180
        valueCellIndexPath = [NSIndexPath indexPathForRow:1 inSection:kGroupDetails];
181
        cell = [self.tableView cellForRowAtIndexPath:valueCellIndexPath];
182
        textField = [[cell.contentView subviews] objectAtIndex:0];
183
        NSString *newGroupUsers = textField.text;
184
        
185
        if (![self userInputIsValid:newGroupUsers fieldName:@"Group users"]) {
209
    if ((indexPath.section == kGroupUsers) && (indexPath.row == numberOfGroupUsersRows - 1)) {
210
        numberOfGroupUsersRows++;
211
        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
212
        [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kGroupUsers] withRowAnimation:UITableViewRowAnimationNone];
213
    } else if (indexPath.section == kSave) {
214
        if (![self groupNameIsValid:groupNameTextField.text]) {
186 215
            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
187
            return;
216
        } else {
217
            NSMutableArray *newGroupUsers = [NSMutableArray arrayWithCapacity:groupUsersTextFields.count];
218
            for (UITextField *groupUserTextField in [groupUsersTextFields allValues]) {
219
                if (groupUserTextField.text && groupUserTextField.text.length)
220
                    [newGroupUsers addObject:groupUserTextField.text];
221
            }
222
            if (!newGroupUsers.count) {
223
                [self alert:@"Cannot save group" message:@"Please input at least one user."];
224
                [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
225
            } else {
226
                __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Checking users..."
227
                                                                                                               andAddToView:self.view];
228
                [[self.account.manager userCatalogForDisplaynames:newGroupUsers UUIDs:nil]
229
                 success:^(OpenStackRequest *request) {
230
                     NSDictionary *displaynameCatalog = [request displaynameCatalog];
231
                     NSMutableArray *newGroupUUIDs = [NSMutableArray arrayWithCapacity:newGroupUsers.count];
232
                     for (NSString *groupUser in newGroupUsers) {
233
                         NSString *UUID = [displaynameCatalog objectForKey:groupUser];
234
                         if (!UUID) {
235
                             [activityIndicatorView removeFromSuperview];
236
                             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
237
                             [self alert:@"Invalid user" message:[NSString stringWithFormat:@"User '%@' doesn't exist.", groupUser]];
238
                             return;
239
                         }
240
                         [newGroupUUIDs addObject:UUID];
241
                     }
242
                     // Apply changes.
243
                     [groups removeObjectForKey:groupName];
244
                     [groups setObject:newGroupUUIDs forKey:groupNameTextField.text];
245
                     [activityIndicatorView removeFromSuperview];
246
                    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Saving group..."
247
                                                                                                                   andAddToView:self.view];
248
                     [[self.account.manager writeAccountMetadata:[NSDictionary dictionaryWithObjectsAndKeys:
249
                                                                  [self groupsInfoForGroups:groups], @"groups",
250
                                                                  metadata, @"metadata",
251
                                                                  nil]]
252
                      success:^(OpenStackRequest *request) {
253
                          self.groupName = groupNameTextField.text;
254
                          self.groupUUIDs = newGroupUUIDs;
255
                          numberOfGroupUsersRows = groupUUIDs.count + 1;
256
                          self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:groupUUIDs.count];
257
                          removeGroupEnabled = YES;
258
                          self.navigationItem.title = @"Edit Group";
259
                          [activityIndicatorView removeFromSuperview];
260
                          [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
261
                          [self.tableView reloadData];
262
                     }
263
                     failure:^(OpenStackRequest *request) {
264
                         [groups removeObjectForKey:groupNameTextField.text];
265
                         if (groupUUIDs.count) {
266
                             [groups setObject:groupUUIDs forKey:groupName];
267
                         }
268
                         [activityIndicatorView removeFromSuperview];
269
                         [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
270
                         [self alert:@"There was a problem saving the group." request:request];
271
                     }];
272
                 }
273
                 failure:^(OpenStackRequest *request) {
274
                     [activityIndicatorView removeFromSuperview];
275
                     [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
276
                     [self alert:@"There was a problem checking the users." request:request];
277
                 }];
278
            }
188 279
        }
189

  
190
        [groups removeObjectForKey:groupName];
191
        [groups setObject:newGroupUsers forKey:newGroupName];
192
        NSDictionary *accountInfo = [NSDictionary dictionaryWithObjectsAndKeys:groups, @"groups",
193
                                        metadata, @"metadata",
194
                                        nil
195
                                        ];
196
        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Saving group..."
197
                                                                                                       andAddToView:self.view];
198
        [[self.account.manager writeAccountMetadata:accountInfo]
199
         success:^(OpenStackRequest *request) {
200
             [activityIndicatorView removeFromSuperview];
201
             self.groupName = newGroupName;
202
             self.groupUsers = newGroupUsers;
203
             self.removeGroupEnabled = YES;
204
             self.navigationItem.title = @"Edit Group";
205
             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
206
             [self.tableView reloadData]; 
207
         }
208
         failure:^(OpenStackRequest *request) {
209
             [activityIndicatorView removeFromSuperview];
210
             [groups removeObjectForKey:newGroupName];
211
             [groups setObject:self.groupUsers forKey:self.groupName];
212
             [self.tableView reloadData];
213
             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
214
             [self alert:@"There was a problem saving the group information." request:request];
215
         }];
216 280
    } else if (indexPath.section == kRemove) {
217 281
        [groups removeObjectForKey:groupName];
218
        NSDictionary *accountInfo = [NSDictionary dictionaryWithObjectsAndKeys:groups, @"groups",
219
                                        metadata, @"metadata",
220
                                        nil
221
                                        ];
222 282
        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Removing group..."
223 283
                                                                                                       andAddToView:self.view];
224
        [[self.account.manager writeAccountMetadata:accountInfo]
284
        [[self.account.manager writeAccountMetadata:[NSDictionary dictionaryWithObjectsAndKeys:
285
                                                     [self groupsInfoForGroups:groups], @"groups",
286
                                                     metadata, @"metadata",
287
                                                     nil]]
225 288
         success:^(OpenStackRequest *request) {
226
             [activityIndicatorView removeFromSuperview];
227 289
             self.groupName = @"";
228
             self.groupUsers = @"";
229
             self.removeGroupEnabled = NO;
230
             [self.tableView reloadData];
290
             self.groupUUIDs = [NSArray array];
291
             numberOfGroupUsersRows = 2;
292
             self.groupUsersTextFields = [NSMutableDictionary dictionaryWithCapacity:1];
293
             removeGroupEnabled = NO;
294
             self.navigationItem.title = @"Add Group";
295
             [activityIndicatorView removeFromSuperview];
231 296
             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
297
             [self.tableView reloadData];
232 298
         }
233 299
         failure:^(OpenStackRequest *request) {
300
             if (groupUUIDs.count) {
301
                 [groups setObject:groupUUIDs forKey:groupName];
302
             }
234 303
             [activityIndicatorView removeFromSuperview];
235
             [groups setObject:groupUsers forKey:groupName];
236 304
             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
237
             [self alert:@"There was a problem removing the group information." request:request];
305
             [self alert:@"There was a problem removing the group." request:request];
238 306
         }];
239 307
    }
240 308
}
......
242 310
#pragma mark - UITextFieldDelegate
243 311

  
244 312
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
245
    if ([textField returnKeyType] == UIReturnKeyNext) {
246
        [self userInputIsValid:textField.text fieldName:@"Group name"];
247

  
248
        NSInteger nextTag = [textField tag] + 1;
249
        UIView *nextTextField = [self.tableView viewWithTag:nextTag];
250
        [nextTextField becomeFirstResponder];
251
    } else {
252
        [self userInputIsValid:textField.text fieldName:@"Group users"];
313
    if (textField == groupNameTextField) {
314
        if (![self groupNameIsValid:textField.text]) {
315
            return NO;
316
        }
317
        [[groupUsersTextFields objectForKey:[NSIndexPath indexPathForRow:0 inSection:kGroupUsers]] becomeFirstResponder];
318
    } else if (textField.tag == numberOfGroupUsersRows - 2) {
253 319
        [textField resignFirstResponder];
254
    }
255
    
320
    } else {
321
        [[groupUsersTextFields objectForKey:[NSIndexPath indexPathForRow:(textField.tag + 1) inSection:kGroupUsers]] becomeFirstResponder];
322
    }    
256 323
    return YES;
257 324
}
258 325

  
b/Classes/EditPermissionsViewController.h
35 35
// interpreted as representing official policies, either expressed
36 36
// or implied, of GRNET S.A.
37 37

  
38
@class FolderViewController, OpenStackRequest, OpenStackAccount, Container, StorageObject;
38
@class FolderViewController, OpenStackAccount, Container, StorageObject;
39 39

  
40 40
@interface EditPermissionsViewController : UITableViewController <UITextFieldDelegate, UIAlertViewDelegate> {
41 41
    OpenStackAccount *account;
b/Classes/EditPermissionsViewController.m
102 102
}
103 103

  
104 104
- (void)applyPermissions {
105
    [self.permissions removeObjectForKey:permissionUser];
105
    [permissions removeObjectForKey:permissionUser];
106 106
    NSString *newPermissionUser = ((groupTextField.text && groupTextField.text.length) ?
107 107
                                   [NSString stringWithFormat:@"%@:%@", currentUUID, groupTextField.text] :
108 108
                                   currentUUID);
109 109
    [permissions setObject:(writePermissionSelected ? @"write" : @"read") forKey:newPermissionUser];
110 110
    self.oldSharing = object.sharing;
111 111
    [object setPermissions:permissions];
112
        __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Applying permissions..."
113
                                                                                                       andAddToView:self.view];
114
        [[self.account.manager writeObjectMetadata:container object:object]
115
         success:^(OpenStackRequest *request) {
116
             self.permissionUser = newPermissionUser;
112
    __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Applying permissions..."
113
                                                                                                   andAddToView:self.view];
114
    [[self.account.manager writeObjectMetadata:container object:object]
115
     success:^(OpenStackRequest *request) {
116
         self.permissionUser = newPermissionUser;
117 117

  
118
             [activityIndicatorView removeFromSuperview];
119
             [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
120
             if (!removePermissionsEnabled) {
121
                 removePermissionsEnabled = YES;
122
                 [self.tableView reloadData];
123
             }
124
             
125
             if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
126
                 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
127
                     [self.folderViewController refreshButtonPressed:nil];
128
                 } else {
129
                     self.folderViewController.needsRefreshing = YES;
130
                     self.folderViewController.refreshWhenAppeared = YES;
131
                 }
118
         [activityIndicatorView removeFromSuperview];
119
         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
120
         if (!removePermissionsEnabled) {
121
             removePermissionsEnabled = YES;
122
             self.navigationItem.title = @"Edit Permission";
123
             [self.tableView reloadData];
124
         }
125
         
126
         if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
127
             if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
128
                 [self.folderViewController refreshButtonPressed:nil];
129
             } else {
130
                 self.folderViewController.needsRefreshing = YES;
131
                 self.folderViewController.refreshWhenAppeared = YES;
132 132
             }
133 133
         }
134
         failure:^(OpenStackRequest *request) {
135
             object.sharing = self.oldSharing;
136
             self.permissions = object.permissions;
137
             [activityIndicatorView removeFromSuperview];
138
             [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
139
             [self alert:@"There was a problem applying the permissions." request:request];
140
         }];
141
    
134
     }
135
     failure:^(OpenStackRequest *request) {
136
         object.sharing = self.oldSharing;
137
         self.permissions = object.permissions;
138
         [activityIndicatorView removeFromSuperview];
139
         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
140
         [self alert:@"There was a problem applying the permissions." request:request];
141
     }];    
142 142
}
143 143

  
144 144
- (void)createNewFolder {
......
201 201
    if (indexPath.row == 0) {
202 202
        if (!userTextField) {
203 203
            self.userTextField = [self textFieldForCell:cell];
204
            userTextField.returnKeyType = UIReturnKeyNext;
204 205
            userTextField.placeholder = @"User";
205 206
            NSRange rangeOfColumn = [permissionUser rangeOfString:@":"];
206 207
            userTextField.text = [self.account displaynameForUUID:((rangeOfColumn.location == NSNotFound) ? permissionUser : [permissionUser substringToIndex:rangeOfColumn.location])
......
211 212
    } else if (indexPath.row == 1) {
212 213
        if (!groupTextField) {
213 214
            self.groupTextField = [self textFieldForCell:cell];
215
            groupTextField.returnKeyType = UIReturnKeyDefault;
214 216
            groupTextField.placeholder = @"Group (optional)";
215 217
            NSRange rangeOfColumn = [permissionUser rangeOfString:@":"];
216 218
            groupTextField.text = ((rangeOfColumn.location == NSNotFound) || (rangeOfColumn.location == permissionUser.length - 1)) ? nil : [permissionUser substringFromIndex:(rangeOfColumn.location + 1)];
......
222 224
}
223 225

  
224 226
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
227
    if (indexPath.section == kUser) {
228
        static NSString *CellIdentifier = @"TextFieldCell";
229
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
230
        if (cell == nil) {
231
            cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
232
        }
233
        cell.textLabel.text = nil;
234
        cell.accessoryType = UITableViewCellAccessoryNone;
235
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
236
        for (UIView *view in cell.contentView.subviews) {
237
            [view removeFromSuperview];
238
        }
239
        [cell.contentView addSubview:[self textFieldForCell:cell withIndexPath:indexPath]];
240
        return cell;
241
    }
242
    
225 243
    static NSString *CellIdentifier = @"Cell";
226 244
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
227 245
    if (cell == nil) {
228 246
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
229 247
    }
230 248
    
231
    if (indexPath.section == kUser) {
232
        [cell.contentView addSubview:[self textFieldForCell:cell withIndexPath:indexPath]];
233
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
234
    } else if (indexPath.section == kPermissions) {
249
    if (indexPath.section == kPermissions) {
235 250
        if (indexPath.row == 0) {
236 251
            cell.textLabel.text = @"Read Only";
237 252
            cell.accessoryType = (readPermissionSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone);
......
261 276
    if (indexPath.section != kUser)
262 277
        [self.view endEditing:YES];
263 278
    
264
    if (![self userIsValid:userTextField.text] || ![self groupIsValid:groupTextField.text]) {
265
        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
266
    } else if (indexPath.section == kPermissions) {
279
    if (indexPath.section == kPermissions) {
267 280
        if ((indexPath.row == 0) && !readPermissionSelected) {
268 281
            readPermissionSelected = YES;
269 282
            if (writePermissionSelected) {
......
280 293
            writePermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
281 294
        }
282 295
    } else if (indexPath.section == kSavePermissions) {
283
        if (!readPermissionSelected && !writePermissionSelected) {
296
        if (![self userIsValid:userTextField.text] || ![self groupIsValid:groupTextField.text]) {
297
            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
298
        } else if (!readPermissionSelected && !writePermissionSelected) {
284 299
            [self alert:@"Cannot save permissions"  message:@"Please select a permission type."];
285 300
            [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
286 301
        } else {
......
333 348
             self.readPermissionSelected = FALSE;
334 349
             self.writePermissionSelected = FALSE;
335 350
             removePermissionsEnabled = NO;
351
             self.navigationItem.title = @"Add Permission";
336 352
             [self.tableView reloadData];
337 353

  
338 354
             if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
......
358 374

  
359 375
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
360 376
    if (textField == userTextField) {
361
        [self userIsValid:textField.text];
377
        if (![self userIsValid:textField.text]) {
378
            return NO;
379
        }
380
        [groupTextField becomeFirstResponder];
362 381
    } else if (textField == groupTextField) {
363
        [self groupIsValid:textField.text];
382
        if (![self groupIsValid:textField.text]) {
383
            return NO;
384
        }
385
        [groupTextField resignFirstResponder];
364 386
    }
365
    [textField resignFirstResponder];
366 387
    return YES;
367 388
}
368 389

  
b/Classes/FolderDetailViewController.m
348 348
        if (indexPath.row == permissions.count) {
349 349
            user = @"";
350 350
            vc.removePermissionsEnabled = NO;
351
            vc.navigationItem.title = @"Share";
351
            vc.navigationItem.title = @"Add Permission";
352 352
        } else {
353 353
            user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
354 354
            NSString *userPermissions = [permissions objectForKey:user];
......
363 363
                vc.writePermissionSelected = NO;
364 364
            }
365 365
            vc.removePermissionsEnabled = YES;
366
            vc.navigationItem.title = @"Edit Sharing";
366
            vc.navigationItem.title = @"Edit Permission";
367 367
        }
368 368
        
369 369
        vc.permissionUser = user;
b/Classes/OpenStackRequest.m
56 56
    NSMutableString *dataString = [NSMutableString stringWithString:@"{\"displaynames\":["];
57 57
    if (displaynames) {
58 58
        for (NSUInteger index = 0 ; index < displaynames.count ; index++) {
59
            [dataString appendFormat:@"\"%@\"%@", [displaynames objectAtIndex:index], ((index == displaynames.count - 1) ? @"" : @",")];
59
            [dataString appendFormat:@"\"%@\"%@",
60
             [[[displaynames objectAtIndex:index] stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]
61
              stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""],
62
             ((index == displaynames.count - 1) ? @"" : @",")];
60 63
        }
61 64
    }
62 65
    [dataString appendFormat:@"],\"uuids\":["];
......
133 136
    OpenStackRequest *request = [self filesRequest:account method:@"POST" path:@""];
134 137

  
135 138
    NSMutableDictionary *groups = [accountInfo objectForKey:@"groups"];
136
    for (NSString *groupName in groups) {
137
        NSString *group = [NSString encodeToPercentEscape:[groups objectForKey:groupName]];
138
        groupName = [NSString encodeToPercentEscape:groupName];
139
        [request.requestHeaders setObject:group forKey:[NSString stringWithFormat:@"X-Account-Group-%@", groupName]];
140
    }
141
    if ([groups count] == 0)
139
    if (groups.count) {
140
        for (NSString *groupName in groups) {
141
            [request.requestHeaders setObject:[NSString encodeToPercentEscape:[groups objectForKey:groupName]]
142
                                       forKey:[NSString stringWithFormat:@"X-Account-Group-%@", [NSString encodeToPercentEscape:groupName]]];
143
        }
144
    } else {
142 145
        [request.requestHeaders setObject:@"" forKey:@"X-Account-Group-group"];
146
    }
143 147
    
144
    NSMutableDictionary *accountMetadata = [accountInfo objectForKey:@"metadata"];
145
    for (NSString *metadataKey in accountMetadata) {
146
        NSString *metadataValue = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
147
        metadataKey = [NSString encodeToPercentEscape:[accountMetadata objectForKey:metadataKey]];
148
        [request.requestHeaders setObject:metadataValue forKey:[NSString stringWithFormat:@"X-Account-Meta-%@",metadataKey]];
148
    NSMutableDictionary *metadata = [accountInfo objectForKey:@"metadata"];
149
    for (NSString *key in metadata) {
150
        [request.requestHeaders setObject:[NSString encodeToPercentEscape:[metadata objectForKey:key]]
151
                                   forKey:[NSString stringWithFormat:@"X-Account-Meta-%@", [NSString encodeToPercentEscape:key]]];
149 152
    }
150 153
    
151 154
    return request;
b/Classes/StorageObject.m
163 163
            }
164 164
        }
165 165
    }
166
    if (readSharingList && writeSharingList) {
167
        self.sharing = [NSString stringWithFormat:@"%@;%@", readSharingList, writeSharingList];
168
    } else if (readSharingList) {
169
        self.sharing = readSharingList;
166
    if (readSharingList) {
167
        if (writeSharingList) {
168
            self.sharing = [NSString stringWithFormat:@"%@;%@", readSharingList, writeSharingList];
169
        } else {
170
            self.sharing = readSharingList;
171
        }
170 172
    } else if (writeSharingList) {
171 173
        self.sharing = writeSharingList;
174
    } else {
175
        self.sharing = @"";
172 176
    }
173 177
}
174 178

  
b/Classes/StorageObjectViewController.m
155 155
}
156 156

  
157 157
- (void)viewDidAppear:(BOOL)animated {
158
    if (object.metadata == nil) {
158
    [super viewDidAppear:animated];
159
    if (!object.metadata) {
159 160
        [self reloadMetadataSection];
160 161
    } else {
161 162
        [self updatePermissionsUserCatalog];
......
289 290

  
290 291
- (void)updatePermissionsUserCatalog {
291 292
    NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:permissions.count];
292
    for (NSString *user in [permissions allKeys]) {
293
    for (NSString *user in permissions) {
293 294
        NSRange rangeOfColumn = [user rangeOfString:@":"];
294 295
        NSString *UUID = (rangeOfColumn.location == NSNotFound) ? user : [user substringToIndex:rangeOfColumn.location];
295 296
        if (![UUID isEqualToString:@"*"]) {
......
306 307
         }
307 308
         failure:^(OpenStackRequest *request) {
308 309
             [activityIndicatorView removeFromSuperview];
310
             [self alert:@"Failed to translate sharing UUIDs." request:request];
309 311
         }];
310 312
    }
311 313
}
......
674 676
        if (indexPath.row == permissions.count) {
675 677
            user = @"";
676 678
            vc.removePermissionsEnabled = NO;
677
            vc.navigationItem.title = @"Share";
679
            vc.navigationItem.title = @"Add Permission";
678 680
        } else {
679 681
            user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
680 682
            NSString *userPermissions = [permissions objectForKey:user];
......
689 691
                vc.writePermissionSelected = NO;
690 692
            }
691 693
            vc.removePermissionsEnabled = YES;
692
            vc.navigationItem.title = @"Edit Sharing";
694
            vc.navigationItem.title = @"Edit Permission";
693 695
        }
694 696
        
695 697
        vc.permissionUser = user;
b/OpenStack-Info.plist
41 41
	<key>CFBundlePackageType</key>
42 42
	<string>APPL</string>
43 43
	<key>CFBundleShortVersionString</key>
44
	<string>1.5.1</string>
44
	<string>1.6</string>
45 45
	<key>CFBundleSignature</key>
46 46
	<string>????</string>
47 47
	<key>CFBundleURLTypes</key>
......
56 56
		</dict>
57 57
	</array>
58 58
	<key>CFBundleVersion</key>
59
	<string>20130122.0</string>
59
	<string>20130203.2</string>
60 60
	<key>LSRequiresIPhoneOS</key>
61 61
	<true/>
62 62
	<key>NSMainNibFile</key>

Also available in: Unified diff