Update version
[pithos-ios] / Classes / EditPermissionsViewController.m
1 //
2 //  EditPermissionsViewController.m
3 //  pithos-ios
4 //
5 // Copyright 2011-2013 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
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.
19 // 
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.
32 // 
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.
37
38 #import "EditPermissionsViewController.h"
39 #import "AccountManager.h"
40 #import "UIViewController+Conveniences.h"
41 #import "FolderViewController.h"
42 #import "OpenStackAccount.h"
43 #import "Container.h"
44 #import "Folder.h"
45 #import "StorageObject.h"
46 #import "ActivityIndicatorView.h"
47 #import "PithosUtilities.h"
48 #import "APICallback.h"
49
50 #define kUser 0
51 #define kPermissions 1
52 #define kSavePermissions 2
53 #define kRemovePermissions 3
54
55 @implementation EditPermissionsViewController
56
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;
62
63 #pragma mark - View lifecycle
64
65 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
66     return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
67 }
68
69 #pragma mark - Memory management
70
71 - (void)dealloc {
72     [account release];
73     [container release];
74     [object release];
75     [permissionUser release];
76     [permissions release];
77     [oldSharing release];
78     [currentUUID release];
79     [userTextField release];
80     [groupTextField release];
81     [readPermissionsCell release];
82     [writePermissionsCell release];
83     [super dealloc];
84 }
85
86 #pragma mark - Internal
87
88 - (BOOL)userIsValid:(NSString *)input {
89     if (!input.length) {
90         [self alert:@"Invalid input" message:@"User cannot be empty."];
91         return NO;
92     }
93     return YES;
94 }
95
96 - (BOOL)groupIsValid:(NSString *)input {
97     if (input && ([input rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound)) {
98         [self alert:@"Invalid input" message:@"Group cannot contain ' ', '-', '_', '~', ',' or ';'."];
99         return NO;
100     }
101     return YES;
102 }
103
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] :
109                                    newUUID);
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;
118
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];
125          }
126          
127          if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
128              if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
129                  [self.folderViewController refreshButtonPressed:nil];
130              } else {
131                  self.folderViewController.refreshWhenAppeared = YES;
132              }
133          }
134      }
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];
141      }];    
142 }
143
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?
157          [newFolder release];
158          
159          if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
160              [self.folderViewController refreshButtonPressed:nil];
161          } else {
162              self.folderViewController.refreshWhenAppeared = YES;
163          }
164          [self applyPermissions];
165      }
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];
170      }];
171 }
172
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]
181                                                                 delegate:self
182                                                        cancelButtonTitle:@"Cancel"
183                                                        otherButtonTitles:@"OK", nil] autorelease];
184                 [alert show];
185             } else {
186                 [self createNewFolder];
187             }
188     } else {
189         [self applyPermissions];
190     }
191 }
192
193 #pragma mark - UITableViewDataSource
194
195 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
196     return (removePermissionsEnabled ? 4 : 3);
197 }
198
199 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
200     if ((section == kUser) || (section == kPermissions)) {
201         return 2;
202     }
203     return 1;
204 }
205
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;
216     return textField;
217 }
218
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])
227                                                              safe:YES];
228             userTextField.delegate = self;
229         }
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;
239         }
240         return groupTextField;
241     }
242     return nil;
243 }
244
245 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
246     if (indexPath.section == kUser) {
247         static NSString *CellIdentifier = @"TextFieldCell";
248         UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
249         if (cell == nil) {
250             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
251         }
252         cell.textLabel.text = nil;
253         cell.accessoryType = UITableViewCellAccessoryNone;
254         cell.selectionStyle = UITableViewCellSelectionStyleNone;
255         for (UIView *view in cell.contentView.subviews) {
256             [view removeFromSuperview];
257         }
258         [cell.contentView addSubview:[self textFieldForCell:cell withIndexPath:indexPath]];
259         return cell;
260     }
261     
262     static NSString *CellIdentifier = @"Cell";
263     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
264     if (cell == nil) {
265         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
266     }
267     
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;
273         } else {
274             cell.textLabel.text = @"Read/Write";
275             cell.accessoryType = (writePermissionSelected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone);
276             self.writePermissionsCell = cell;
277         }
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;
287     }
288     
289     return cell;
290 }
291
292 #pragma mark - UITableViewDelegate
293
294 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
295     if (indexPath.section != kUser)
296         [self.view endEditing:YES];
297     
298     if (indexPath.section == kPermissions) {
299         if ((indexPath.row == 0) && !readPermissionSelected) {
300             readPermissionSelected = YES;
301             if (writePermissionSelected) {
302                 writePermissionSelected = FALSE;
303                 writePermissionsCell.accessoryType = UITableViewCellAccessoryNone;
304             }
305             readPermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
306         } else if ((indexPath.row == 1) && !writePermissionSelected) {
307             writePermissionSelected = YES;
308             if (readPermissionSelected) {
309                 readPermissionSelected = FALSE;
310                 readPermissionsCell.accessoryType = UITableViewCellAccessoryNone;
311             }
312             writePermissionsCell.accessoryType = UITableViewCellAccessoryCheckmark;
313         }
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];
320         } else {
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]);
328                  if (currentUUID) {
329                      [self checkObject];
330                  } else {
331                      [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
332                      [self alert:@"Invalid user" message:[NSString stringWithFormat:@"User '%@' doesn't exist.", userTextField.text]];
333                  }
334              }
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 ':'."];
345                  } else {
346                      self.currentUUID = nil;
347                      [self checkObject];
348                  }
349              }];
350         }
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];
367
368              if (objectIsFolder || (account.shared && ![oldSharing isEqualToString:object.sharing])) {
369                  if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
370                      [self.folderViewController refreshButtonPressed:nil];
371                  } else {
372                      self.folderViewController.refreshWhenAppeared = YES;
373                  }
374              }
375          }
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];
382          }];
383     }
384 }
385
386 #pragma mark - UITextFieldDelegate
387
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 ';'."];
392         return NO;
393     }
394     return YES;
395 }
396
397 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
398     if (textField == userTextField) {
399         if (![self userIsValid:textField.text]) {
400             return NO;
401         }
402         [groupTextField becomeFirstResponder];
403     } else if (textField == groupTextField) {
404         if (![self groupIsValid:textField.text]) {
405             return NO;
406         }
407         [groupTextField resignFirstResponder];
408     }
409     return YES;
410 }
411
412 #pragma mark - UIAlertViewDelegate
413
414 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
415     if (buttonIndex == 1) {
416         [self createNewFolder];
417     } else {
418         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
419     }
420 }
421
422 @end