2 // EditPermissionsViewController.m
5 // Copyright 2011-2013 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "EditPermissionsViewController.h"
39 #import "AccountManager.h"
40 #import "UIViewController+Conveniences.h"
41 #import "FolderViewController.h"
42 #import "OpenStackAccount.h"
45 #import "StorageObject.h"
46 #import "ActivityIndicatorView.h"
47 #import "PithosUtilities.h"
48 #import "APICallback.h"
51 #define kPermissions 1
52 #define kSavePermissions 2
53 #define kRemovePermissions 3
55 @implementation EditPermissionsViewController
57 @synthesize account, container, object, folderViewController;
58 @synthesize permissionUser, readPermissionSelected, writePermissionSelected, permissions;
59 @synthesize currentUUID;
60 @synthesize oldSharing, removePermissionsEnabled, objectIsFolder;
61 @synthesize userTextField, groupTextField, readPermissionsCell, writePermissionsCell;
63 #pragma mark - View lifecycle
65 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
66 return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
69 #pragma mark - Memory management
75 [permissionUser release];
76 [permissions release];
78 [currentUUID release];
79 [userTextField release];
80 [groupTextField release];
81 [readPermissionsCell release];
82 [writePermissionsCell release];
86 #pragma mark - Internal
88 - (BOOL)userIsValid:(NSString *)input {
90 [self alert:@"Invalid input" message:@"User cannot be empty."];
96 - (BOOL)groupIsValid:(NSString *)input {
97 if (input && ([input rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound)) {
98 [self alert:@"Invalid input" message:@"Group cannot contain ' ', '-', '_', '~', ',' or ';'."];
104 - (void)applyPermissions {
105 [permissions removeObjectForKey:permissionUser];
106 NSString *newUUID = (currentUUID ? currentUUID : userTextField.text);
107 NSString *newPermissionUser = ((groupTextField.text && groupTextField.text.length) ?
108 [NSString stringWithFormat:@"%@:%@", newUUID, groupTextField.text] :
110 [permissions setObject:(writePermissionSelected ? @"write" : @"read") forKey:newPermissionUser];
111 self.oldSharing = object.sharing;
112 [object setPermissions:permissions];
113 __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Applying permissions..."
114 andAddToView:self.view];
115 [[self.account.manager writeObjectMetadata:container object:object]
116 success:^(OpenStackRequest *request) {
117 self.permissionUser = newPermissionUser;
119 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
120 [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
121 if (!removePermissionsEnabled) {
122 removePermissionsEnabled = YES;
123 self.navigationItem.title = @"Edit Permission";
124 [self.tableView reloadData];
127 if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
128 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
129 [self.folderViewController refreshButtonPressed:nil];
131 self.folderViewController.refreshWhenAppeared = YES;
135 failure:^(OpenStackRequest *request) {
136 object.sharing = self.oldSharing;
137 self.permissions = object.permissions;
138 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
139 [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
140 [self alert:@"There was a problem applying the permissions." request:request];
144 - (void)createNewFolder {
145 __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Creating folder..."
146 andAddToView:self.view];
147 object.contentType = @"application/directory";
148 object.data = [NSData data];
149 [[self.account.manager writeObject:self.container object:object downloadProgressDelegate:nil]
150 success:^(OpenStackRequest *request) {
151 Folder *newFolder = [[Folder alloc] init];
152 newFolder.name = [[object.name componentsSeparatedByString:@"/"] lastObject];
153 newFolder.parent = folderViewController.folder;
154 newFolder.sharing = folderViewController.folder.sharing;
155 [folderViewController.folder addFolder:newFolder];
156 // XXX increase container.count if in rootFolder?
159 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
160 [self.folderViewController refreshButtonPressed:nil];
162 self.folderViewController.refreshWhenAppeared = YES;
164 [self applyPermissions];
166 failure:^(OpenStackRequest *request) {
167 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
168 [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
169 [self alert:@"There was a problem creating the folder." request:request];
173 - (void)checkObject {
174 if (objectIsFolder && ![PithosUtilities isContentTypeDirectory:object.contentType]) {
175 if ((([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) &&
176 [folderViewController.parentFolderViewController.folder.objects objectForKey:object.name]) ||
177 (([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) &&
178 [folderViewController.folder.objects objectForKey:object.name])) {
179 UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Apply changes"
180 message:[NSString stringWithFormat:@"In order to apply the changes to folder '%@', the file at the same path must be replaced. Continue?", object.name]
182 cancelButtonTitle:@"Cancel"
183 otherButtonTitles:@"OK", nil] autorelease];
186 [self createNewFolder];
189 [self applyPermissions];
193 #pragma mark - UITableViewDataSource
195 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
196 return (removePermissionsEnabled ? 4 : 3);
199 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
200 if ((section == kUser) || (section == kPermissions)) {
206 - (UITextField *)textFieldForCell:(UITableViewCell *)cell {
207 CGRect rect = CGRectInset(cell.contentView.bounds, 10.0, 10.0);
208 UITextField *textField = [[[UITextField alloc] initWithFrame:rect] autorelease];
209 textField.frame = rect;
210 textField.clearButtonMode = UITextFieldViewModeWhileEditing;
211 textField.backgroundColor = [UIColor clearColor];
212 textField.opaque = YES;
213 textField.autocorrectionType = UITextAutocorrectionTypeNo;
214 textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
215 textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
219 - (UITextField *)textFieldForCell:(UITableViewCell *)cell withIndexPath:(NSIndexPath *)indexPath {
220 if (indexPath.row == 0) {
221 if (!userTextField) {
222 self.userTextField = [self textFieldForCell:cell];
223 userTextField.returnKeyType = UIReturnKeyNext;
224 userTextField.placeholder = @"User";
225 NSRange rangeOfColumn = [permissionUser rangeOfString:@":"];
226 userTextField.text = [self.account displaynameForUUID:((rangeOfColumn.location == NSNotFound) ? permissionUser : [permissionUser substringToIndex:rangeOfColumn.location])
228 userTextField.delegate = self;
230 return userTextField;
231 } else if (indexPath.row == 1) {
232 if (!groupTextField) {
233 self.groupTextField = [self textFieldForCell:cell];
234 groupTextField.returnKeyType = UIReturnKeyDefault;
235 groupTextField.placeholder = @"Group (optional)";
236 NSRange rangeOfColumn = [permissionUser rangeOfString:@":"];
237 groupTextField.text = ((rangeOfColumn.location == NSNotFound) || (rangeOfColumn.location == permissionUser.length - 1)) ? nil : [permissionUser substringFromIndex:(rangeOfColumn.location + 1)];
238 groupTextField.delegate = self;
240 return groupTextField;
245 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
246 if (indexPath.section == kUser) {
247 static NSString *CellIdentifier = @"TextFieldCell";
248 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
250 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
252 cell.textLabel.text = nil;
253 cell.accessoryType = UITableViewCellAccessoryNone;
254 cell.selectionStyle = UITableViewCellSelectionStyleNone;
255 for (UIView *view in cell.contentView.subviews) {
256 [view removeFromSuperview];
258 [cell.contentView addSubview:[self textFieldForCell:cell withIndexPath:indexPath]];
262 static NSString *CellIdentifier = @"Cell";
263 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
265 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
268 if (indexPath.section == kPermissions) {
269 if (indexPath.row == 0) {
270 cell.textLabel.text = @"Read Only";
271 cell.accessoryType = (readPermissionSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone);
272 self.readPermissionsCell = cell;
274 cell.textLabel.text = @"Read/Write";
275 cell.accessoryType = (writePermissionSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone);
276 self.writePermissionsCell = cell;
278 cell.selectionStyle = UITableViewCellSelectionStyleNone;
279 } else if (indexPath.section == kSavePermissions) {
280 cell.textLabel.text = @"Save";
281 cell.accessoryType = UITableViewCellAccessoryNone;
282 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
283 } else if (indexPath.section == kRemovePermissions) {
284 cell.textLabel.text = @"Remove";
285 cell.accessoryType = UITableViewCellAccessoryNone;
286 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
292 #pragma mark - UITableViewDelegate
294 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
295 if (indexPath.section != kUser)
296 [self.view endEditing:YES];
298 if (indexPath.section == kPermissions) {
299 if ((indexPath.row == 0) && !readPermissionSelected) {
300 readPermissionSelected = YES;
301 if (writePermissionSelected) {
302 writePermissionSelected = FALSE;
303 writePermissionsCell.accessoryType = UITableViewCellAccessoryNone;
305 readPermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
306 } else if ((indexPath.row == 1) && !writePermissionSelected) {
307 writePermissionSelected = YES;
308 if (readPermissionSelected) {
309 readPermissionSelected = FALSE;
310 readPermissionsCell.accessoryType = UITableViewCellAccessoryNone;
312 writePermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
314 } else if (indexPath.section == kSavePermissions) {
315 if (![self userIsValid:userTextField.text] || ![self groupIsValid:groupTextField.text]) {
316 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
317 } else if (!readPermissionSelected && !writePermissionSelected) {
318 [self alert:@"Cannot save permissions" message:@"Please select a permission type."];
319 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
321 __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Checking user..."
322 andAddToView:self.view];
323 [[self.account.manager userCatalogForDisplaynames:[NSArray arrayWithObject:userTextField.text] UUIDs:nil]
324 success:^(OpenStackRequest *request) {
325 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
326 self.currentUUID = ([userTextField.text isEqualToString:@"*"] ?
327 @"*" : [[request displaynameCatalog] objectForKey:userTextField.text]);
331 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
332 [self alert:@"Invalid user" message:[NSString stringWithFormat:@"User '%@' doesn't exist.", userTextField.text]];
335 failure:^(OpenStackRequest *request) {
336 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
337 if (request.responseStatusCode != 404) {
338 // Don't show alert on 404, since it can be a pre-UUID server.
339 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
340 [self alert:@"There was a problem checking the user." request:request];
341 } else if ([userTextField.text rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" ~,;:"]].location != NSNotFound) {
342 // Since we don't translate to UUIDs, check for invalid chars.
343 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
344 [self alert:@"Invalid input" message:@"User cannot contain ' ', '~', ',', ';' or ':'."];
346 self.currentUUID = nil;
351 } else if (indexPath.section == kRemovePermissions) {
352 __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Removing permissions..."
353 andAddToView:self.view];
354 [permissions removeObjectForKey:permissionUser];
355 self.oldSharing = object.sharing;
356 [object setPermissions:permissions];
357 [[self.account.manager writeObjectMetadata:container object:object]
358 success:^(OpenStackRequest *request) {
359 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
360 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
361 self.permissionUser = @"";
362 self.readPermissionSelected = FALSE;
363 self.writePermissionSelected = FALSE;
364 removePermissionsEnabled = NO;
365 self.navigationItem.title = @"Add Permission";
366 [self.tableView reloadData];
368 if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
369 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
370 [self.folderViewController refreshButtonPressed:nil];
372 self.folderViewController.refreshWhenAppeared = YES;
376 failure:^(OpenStackRequest *request) {
377 object.sharing = self.oldSharing;
378 self.permissions = object.permissions;
379 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
380 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
381 [self alert:@"There was a problem removing the permissions." request:request];
386 #pragma mark - UITextFieldDelegate
388 - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
389 if ((textField == groupTextField) &&
390 ([string rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound)) {
391 [self alert:@"Invalid input" message:@"Group cannot contain ' ', '-', '_', '~', ',' or ';'."];
397 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
398 if (textField == userTextField) {
399 if (![self userIsValid:textField.text]) {
402 [groupTextField becomeFirstResponder];
403 } else if (textField == groupTextField) {
404 if (![self groupIsValid:textField.text]) {
407 [groupTextField resignFirstResponder];
412 #pragma mark - UIAlertViewDelegate
414 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
415 if (buttonIndex == 1) {
416 [self createNewFolder];
418 [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];