Update version
[pithos-ios] / Classes / EditMetadataViewController.m
1 //
2 //  EditMetadataViewController.m
3 //  pithos-ios
4 //
5 // Copyright 2011-2012 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 "EditMetadataViewController.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 @implementation EditMetadataViewController
51
52 @synthesize container, account, object;
53
54 @synthesize metadataKey;
55 @synthesize metadataValue;
56 @synthesize userInputMetaKey, userInputMetaValue;
57 @synthesize removeMetadataEnabled, objectIsFolder, folderViewController, objectIsContainer;
58
59 #define kMetadata 0 
60 #define kSaveMedata 1
61 #define kDeleteMetadata 2
62
63
64 - (void)dealloc {
65     [account release];
66     [container release];
67     [object release];
68     [metadataKey release];
69     [metadataValue release];
70     [userInputMetaKey release];
71     [userInputMetaValue release];
72     [super dealloc];
73 }
74
75 #pragma mark - View lifecycle
76
77 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
78     return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
79 }
80
81 #pragma mark - Internal
82
83 - (void)createNewFolder {
84     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Saving metadata..."
85                                                                                                    andAddToView:self.view];
86     [[self.account.manager writeObject:container object:object downloadProgressDelegate:nil]
87      success:^(OpenStackRequest *request) {
88          Folder *newFolder = [[Folder alloc] init];
89          newFolder.name = [[object.name componentsSeparatedByString:@"/"] lastObject];
90          newFolder.parent = folderViewController.folder;
91          newFolder.sharing = folderViewController.folder.sharing;
92          newFolder.metadata = object.metadata;
93          [folderViewController.folder addFolder:newFolder];
94          // XXX increase container.count if in rootFolder?
95          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
96          [newFolder release];
97          self.metadataKey = userInputMetaKey;
98          self.metadataValue = userInputMetaValue;
99          removeMetadataEnabled = YES;
100          [self.tableView reloadData];
101          [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
102          if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
103              [self.folderViewController refreshButtonPressed:nil];
104          } else {
105              self.folderViewController.refreshWhenAppeared = YES;
106          }
107      }
108      failure:^(OpenStackRequest *request) {
109          [object.metadata removeObjectForKey:userInputMetaKey];
110          [object.metadata setObject:metadataValue forKey:metadataKey];
111          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
112          [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
113          [self.tableView reloadData];
114          [self alert:@"There was a problem saving the metadata." request:request];
115      }];
116 }
117
118 #pragma mark - Table view data source
119
120 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
121     return (removeMetadataEnabled ? 3 : 2);
122 }
123
124 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
125     return ((section == kMetadata) ? 2 : 1);
126 }
127
128 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
129     static NSString *CellIdentifier = @"Cell";
130     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
131     if (cell == nil) {
132         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
133     }
134                 
135     if (indexPath.section == kMetadata) {
136         UITextField *textField = nil;
137         for (id subView in cell.contentView.subviews) {
138             if ([subView isKindOfClass:[UITextField class]]) {
139                 textField = (UITextField *)subView;
140             }
141         }
142         
143         if (textField == nil) {
144             CGRect bounds = [cell.contentView bounds];
145             CGRect rect = CGRectInset(bounds, 10.0, 10.0);                        
146             textField = [[UITextField alloc] initWithFrame:rect];
147             [textField setFrame:rect];
148         }
149         [textField setClearButtonMode:UITextFieldViewModeWhileEditing];
150         [textField setBackgroundColor:[UIColor clearColor]];
151         [textField setOpaque:YES];
152         [textField setAutocorrectionType:UITextAutocorrectionTypeNo];
153         [textField setAutocapitalizationType:UITextAutocapitalizationTypeNone];
154         [textField setDelegate:self];
155         textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
156
157         if (indexPath.row == 0) {
158             [textField setReturnKeyType:UIReturnKeyNext];
159             textField.placeholder = @"Key";
160             textField.text = self.metadataKey;
161             textField.tag = 0;
162         } else if (indexPath.row == 1) {
163             textField.placeholder = @"Value";
164             textField.text = self.metadataValue;
165             textField.tag = 1;
166         }
167             
168         cell.selectionStyle = UITableViewCellSelectionStyleNone;
169         [cell.contentView addSubview:textField];
170     } else if (indexPath.section == kSaveMedata) {
171         cell.textLabel.text = @"Save";
172     } else if (indexPath.section == kDeleteMetadata) {
173         cell.textLabel.text = @"Remove";
174     }
175
176     return cell;
177 }
178
179 #pragma mark - Table view delegate
180
181 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
182     if (indexPath.section != kMetadata)
183         [self.view endEditing:YES];
184     
185     NSIndexPath *keyCellIndexPath;
186     NSIndexPath *valueCellIndexPath;
187     UITableViewCell *cell;
188     
189     if (indexPath.section == kSaveMedata) {
190         keyCellIndexPath = [NSIndexPath indexPathForRow:0 inSection:kMetadata];
191         cell = [self.tableView cellForRowAtIndexPath:keyCellIndexPath];
192         UITextField *textField = [[cell.contentView subviews] objectAtIndex:0];
193         self.userInputMetaKey = textField.text;
194     
195         if (!userInputMetaKey.length || ![userInputMetaKey stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]].length) {
196             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
197             [self alert:@"Invalid input" message:@"Metadata key cannot be empty"];
198         } else {
199             valueCellIndexPath = [NSIndexPath indexPathForRow:1 inSection:kMetadata];
200             cell = [self.tableView cellForRowAtIndexPath:valueCellIndexPath];
201             textField = [[cell.contentView subviews] objectAtIndex:0];
202             self.userInputMetaValue = textField.text;
203             [object.metadata removeObjectForKey:metadataKey];
204             [object.metadata setObject:userInputMetaValue forKey:userInputMetaKey];
205             
206             if (objectIsFolder && ![PithosUtilities isContentTypeDirectory:object.contentType]) {
207                 if ((([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) &&
208                      [folderViewController.parentFolderViewController.folder.objects objectForKey:object.name]) ||
209                     (([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) &&
210                      [folderViewController.folder.objects objectForKey:object.name])) {
211                     NSString *alertMessage = [NSString stringWithFormat:@"In order to apply the changes in '%@', the object at the same path must be replaced. Continue?", object.name];
212                     NSString *alertTitle = @"Apply changes";
213                     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:alertTitle
214                                                                     message:alertMessage
215                                                                    delegate:self
216                                                           cancelButtonTitle:@"Cancel"
217                                                           otherButtonTitles:@"OK", nil];
218                     [alert show];
219                     [alert release];
220                 } else {
221                     object.name = object.fullPath;
222                     object.contentType = @"application/directory";
223                     object.data = [NSData data];
224                     [self createNewFolder];
225                 }
226             } else {
227                 __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Saving metadata..."
228                                                                                                                andAddToView:self.view];
229                 [[self.account.manager writeObjectMetadata:container object:object]
230                  success:^(OpenStackRequest *request) {
231                      self.metadataKey = userInputMetaKey;
232                      self.metadataValue = userInputMetaValue;
233                      removeMetadataEnabled = YES;
234                      [self.tableView reloadData];
235                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
236                      [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
237                      if (objectIsContainer) {
238                          container.metadata = object.metadata;
239                          [self.account.containers setObject:container forKey:container.name];
240                      }
241                  }
242                  failure:^(OpenStackRequest *request) { 
243                      [object.metadata removeObjectForKey:userInputMetaKey];
244                      [object.metadata setObject:metadataValue forKey:metadataKey];
245                      [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
246                      [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
247                      [self alert:@"There was a problem saving the metadata." request:request];
248                  }];
249             }
250         }
251     } else if (indexPath.section == kDeleteMetadata) {
252         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Deleting metadata..."
253                                                                                                        andAddToView:self.view];
254             [object.metadata removeObjectForKey:metadataKey];
255             [[self.account.manager writeObjectMetadata:container object:object]
256              success:^(OpenStackRequest *request) {
257                  [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
258                  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
259                  self.metadataKey = @"";
260                  self.metadataValue = @"";
261                  removeMetadataEnabled = NO;
262                  [self.tableView reloadData];
263                  if (objectIsContainer) {
264                      container.metadata = object.metadata;
265                      [self.account.containers setObject:container forKey:container.name];
266                  }
267              }
268              failure:^(OpenStackRequest *request) {
269                  [object.metadata setObject:metadataValue forKey:metadataKey];
270                  [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
271                  [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
272                  [self alert:@"There was a problem saving the metadata." request:request];
273
274              }];
275     }
276 }
277
278 #pragma mark - Alertview delegate
279
280 - (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
281     if (buttonIndex == 1) {
282         object.name = object.fullPath;
283         object.contentType = @"application/directory";
284         object.data = [NSData data];
285         [self createNewFolder];      
286     } else {
287         [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
288     }
289 }
290
291 #pragma mark - Textfield delegate
292
293 - (BOOL)textFieldShouldReturn:(UITextField *)textField {
294     if ([textField returnKeyType] == UIReturnKeyNext) {
295         if (!textField.text.length || ![textField.text stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]) {
296             [self alert:@"Invalid input" message:@"Metadata key cannot be empty"];
297         }
298
299         NSInteger nextTag = [textField tag] + 1;
300         UIView *nextTextField = [self.tableView viewWithTag:nextTag];
301         [nextTextField becomeFirstResponder];
302     } else {
303         [textField resignFirstResponder];
304     }
305     
306     return YES;
307 }
308
309 @end