Fix bugs and improve folder filter UI
[pithos-ios] / Classes / ContainerDetailViewController.m
1 //
2 //  ContainerDetailViewController.m
3 //  OpenStack
4 //
5 //  Created by Mike Mayo on 12/22/10.
6 //  The OpenStack project is provided under the Apache 2.0 license.
7 //
8
9 #import "ContainerDetailViewController.h"
10 #import "OpenStackAccount.h"
11 #import "Container.h"
12 #import "ActivityIndicatorView.h"
13 #import "AccountManager.h"
14 #import "UIViewController+Conveniences.h"
15 #import "UIColor+MoreColors.h"
16 #import "ContainersViewController.h"
17 #import "EditMetadataViewController.h"
18 #import "EditPolicyViewController.h"
19 #import "Folder.h"
20 #import "FolderViewController.h"
21 #import "OpenStackRequest.h"
22 #import "APICallback.h"
23 #import "NSString+Conveniences.h"
24
25 #define kOverview 0
26 #define kMetadata 1
27
28 #define maxMetadataViewableLength 12
29
30 @implementation ContainerDetailViewController
31
32 @synthesize account, container, containersViewController, selectedContainerIndexPath, rootFolderViewController;
33
34 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
35     return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
36 }
37
38 - (void)setBackgroundView {
39     if (!self.container) {
40         UIView *viewContainer = [[UIView alloc] init];
41         viewContainer.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
42         viewContainer.backgroundColor = [UIColor iPadTableBackgroundColor];
43         
44         UILabel *label = [[UILabel alloc] init];
45         label.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;
46         label.backgroundColor = [UIColor clearColor];
47         label.textColor = [UIColor emptyCollectionGrayColor];
48         label.font = [UIFont boldSystemFontOfSize:18.0];
49         label.text = @"No container selected";
50         label.textAlignment = UITextAlignmentCenter;
51         [viewContainer addSubview:label];
52         [label release];
53         
54         self.tableView.backgroundView = viewContainer;
55         [viewContainer release];
56     }
57 }
58
59 #pragma mark -
60 #pragma mark View lifecycle
61
62 - (void)viewDidLoad {
63     [super viewDidLoad];
64     if (!account.sharingAccount) {
65         deleteSection = 3;
66         policySection = 2;
67     } else {
68         deleteSection = -1;
69         policySection = -1;
70     }
71 }
72
73 - (void)viewWillAppear:(BOOL)animated {
74     [super viewWillAppear:animated];
75     if (container) {
76         self.navigationItem.title = container.name;
77     } 
78     [self setBackgroundView];
79     [self.tableView reloadData];
80 }
81
82 - (void)viewDidAppear:(BOOL)animated {
83     [super viewDidAppear:animated];
84     if (self.container && !container.metadata && (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone))
85         [self reloadMetadataSection];
86 }
87
88 - (void)viewWillDisappear:(BOOL)animated {
89     [super viewWillDisappear:animated];
90 }
91
92 #pragma mark -
93 #pragma mark Table view data source
94
95 - (CGFloat)findLabelHeight:(NSString*)text font:(UIFont *)font {
96     CGSize textLabelSize;
97     
98     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
99         textLabelSize = CGSizeMake(577.0, 9000.0f);
100     } else {
101         textLabelSize = CGSizeMake(260.0, 9000.0f);
102     }
103     // pad \n\n to fix layout bug
104     CGSize stringSize = [text sizeWithFont:font constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
105     return stringSize.height;
106 }
107
108 - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
109     if (section == deleteSection) {
110         return @"Only empty containers can be deleted.";
111     } else {
112         return @"";
113     }
114 }
115
116 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
117     if (self.container) {
118         if (account.sharingAccount)
119             return 2;
120         else
121             return 4;
122     } else {
123         return 0;
124     }
125 }
126
127 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
128     // Return the number of rows in the section.
129     if (section == kOverview) {
130         return 2;
131     } else if (section == kMetadata) {
132         if (account.sharingAccount)
133             return [container.metadata count];
134         else
135             return 1 + [container.metadata count];     
136     } else if (section == policySection) {
137         if (account.sharingAccount)
138             return 2;
139         else
140             return 3;
141     } else {
142         return 1;
143     }
144 }
145
146 // Customize the appearance of table view cells.
147 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
148     if (indexPath.section == deleteSection) {
149         static NSString *CellIdentifier = @"DeleteCell";
150         UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
151         if (cell == nil) {
152             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
153             cell.textLabel.textAlignment = UITextAlignmentCenter;
154             cell.textLabel.text = @"Delete Container";
155         }
156         if ((self.container.count == 0) ||
157             (self.container.rootFolder && !self.container.rootFolder.objectsAndFoldersCount)) {
158             cell.textLabel.textColor = [UIColor blackColor];
159             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
160         } else {
161             cell.textLabel.textColor = [UIColor grayColor];
162             cell.selectionStyle = UITableViewCellSelectionStyleNone;
163             cell.userInteractionEnabled = NO;
164         }
165         return cell;
166     }
167
168     static NSString *CellIdentifier = @"Cell";
169     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
170     if (cell == nil) {
171         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
172         cell.selectionStyle = UITableViewCellSelectionStyleNone;
173         cell.textLabel.backgroundColor = [UIColor clearColor];
174         cell.detailTextLabel.backgroundColor = [UIColor clearColor];
175         cell.detailTextLabel.numberOfLines = 0;
176         cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
177         cell.detailTextLabel.textAlignment = UITextAlignmentLeft;
178     }
179     cell.accessoryType = UITableViewCellAccessoryNone;
180     cell.selectionStyle = UITableViewCellSelectionStyleNone;
181     
182     if (indexPath.section == kOverview) {
183         cell.accessoryView = nil;
184         if (indexPath.row == 0) {
185             cell.textLabel.text = @"Name";
186             cell.detailTextLabel.text = container.name;
187         } else if (indexPath.row == 1) {
188             cell.textLabel.text = @"Size";
189             cell.detailTextLabel.text = [container humanizedSize];
190         }
191     } else if (indexPath.section == kMetadata) {
192         if (account.sharingAccount) {
193             cell.accessoryType = UITableViewCellAccessoryNone;
194             cell.selectionStyle = UITableViewCellSelectionStyleNone;
195             cell.userInteractionEnabled = NO;
196         }
197         else {
198             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
199             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
200         }
201         if (indexPath.row == [container.metadata count]) {
202             cell.textLabel.text = @"Add Metadata";
203             cell.detailTextLabel.text = @"";
204         } else {
205             NSString *key = [[container.metadata allKeys] objectAtIndex:indexPath.row];
206             NSString *value = [container.metadata objectForKey:key];
207             NSString *metadataKeyCellText = key;
208             NSString *metadataValueCellText = value;
209             if ([metadataKeyCellText length] > maxMetadataViewableLength) {
210                 metadataKeyCellText = [metadataKeyCellText substringToIndex:(maxMetadataViewableLength - 3)];
211                 metadataKeyCellText = [metadataKeyCellText stringByAppendingString:@"..."];
212             }
213             if ([metadataValueCellText length] > maxMetadataViewableLength) {
214                 metadataValueCellText = [metadataValueCellText substringToIndex:(maxMetadataViewableLength - 3)];
215                 metadataValueCellText = [metadataValueCellText stringByAppendingString:@"..."];
216             }
217             cell.textLabel.text = metadataKeyCellText;
218             cell.detailTextLabel.text = metadataValueCellText;
219         }
220     } else if (indexPath.section == policySection) {
221         cell.selectionStyle = UITableViewCellSelectionStyleBlue;
222         if (indexPath.row == 0) {
223             cell.textLabel.text = @"Versioning";
224             cell.detailTextLabel.text = container.versioning;
225             cell.accessoryType = UITableViewCellAccessoryNone;
226             cell.selectionStyle = UITableViewCellSelectionStyleNone;
227         }
228         else if (indexPath.row == 1) {
229             cell.textLabel.text = @"Quota";
230             cell.detailTextLabel.text = [NSString stringWithFormat:@"%u", container.quota];
231             cell.accessoryType = UITableViewCellAccessoryNone;
232             cell.selectionStyle = UITableViewCellSelectionStyleNone;
233         }
234         else {
235             cell.textLabel.text = @"Edit policy";
236             cell.detailTextLabel.text = @"";
237             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
238         }
239     }
240     
241     return cell;
242 }
243
244 #pragma mark -
245 #pragma mark Table view delegate
246
247 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
248     
249     if (indexPath.section == kMetadata) {
250         EditMetadataViewController *vc = [[EditMetadataViewController alloc] initWithNibName:@"EditMetadataViewController" bundle:nil];
251         NSString *metadataKey;
252         NSString *metadataValue;
253         
254         if (indexPath.row == [self.container.metadata count]) {
255             metadataKey = @"";
256             metadataValue = @"";
257             vc.removeMetadataEnabled = FALSE;
258             vc.navigationItem.title = @"Add Metadata";
259         }
260         else {
261             metadataKey = [[self.container.metadata allKeys] objectAtIndex:indexPath.row];
262             metadataValue = [self.container.metadata objectForKey:metadataKey];
263             vc.removeMetadataEnabled = TRUE;
264             vc.navigationItem.title = @"Edit Metadata";
265         }
266         
267         StorageObject *object = [[[StorageObject alloc] init] autorelease];
268         object.name = container.name;
269         object.metadata = container.metadata;
270         object.fullPath = @"";
271         
272         vc.metadataKey = metadataKey;
273         vc.metadataValue = metadataValue;
274         vc.account = account;
275         vc.container = container;
276         vc.object = object;
277         vc.objectIsContainer = YES;
278         
279         [self.navigationController pushViewController:vc animated:YES];
280         [vc release];
281     } else if (indexPath.section == policySection) {
282         if (indexPath.row == 2) {
283             EditPolicyViewController *vc = [[EditPolicyViewController alloc] initWithNibName:@"EditPolicyViewController" bundle:nil];
284     
285             vc.account = account;
286             vc.container = container;
287             vc.containerDetailViewController = self;
288         
289             [self.navigationController pushViewController:vc animated:YES];
290             [vc release];
291         }
292     } else if (indexPath.section == deleteSection) {
293         if (self.container.count == 0 || !self.container.rootFolder.objectsAndFoldersCount) {
294             UIActionSheet *deleteActionSheet = [[[UIActionSheet alloc] initWithTitle:@"Are you sure you want to delete this container? This operation cannot be undone."
295                                                                             delegate:self
296                                                                    cancelButtonTitle:@"Cancel"
297                                                               destructiveButtonTitle:@"Delete Container"
298                                                                    otherButtonTitles:nil] autorelease];
299             [deleteActionSheet showInView:self.view];
300         }
301     }
302 }
303
304 #pragma mark -
305 #pragma mark Action Sheet
306
307 - (void)deleteContainerRow {
308     if ([self.account.containers count] == 0) {
309         [self.containersViewController.tableView reloadData];
310     } else {
311         [self.containersViewController.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedContainerIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
312     }
313     [self.rootFolderViewController.navigationController popViewControllerAnimated:YES];
314 }
315
316 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
317     if (buttonIndex == 0) {
318      
319         NSString *activityMessage = @"Deleting container...";
320         [activityIndicatorView release];
321         activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
322         [activityIndicatorView addToView:self.view scrollOffset:self.tableView.contentOffset.y];  
323         
324         [[self.account.manager deleteContainer:self.container] success:^(OpenStackRequest *request) {
325             
326             [activityIndicatorView removeFromSuperview];                
327             [self.account.containers removeObjectForKey:self.container.name];
328             [self.account persist];
329             if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
330                 [self.containersViewController.navigationController popViewControllerAnimated:YES];
331             else
332                 [self.navigationController popViewControllerAnimated:YES];
333             
334             [self deleteContainerRow];
335
336             
337         } failure:^(OpenStackRequest *request) {
338             
339             // 404 Not Found means it's not there, so we can show the user that it's deleted
340             if ([request responseStatusCode] == 404) {
341                 [self.account.containers removeObjectForKey:container.name];
342                 [self.account persist];
343                 
344             } else {
345                 [activityIndicatorView removeFromSuperview];
346                 [self alert:@"There was a problem deleting this container." request:request];
347             }
348         }];
349                     
350     }
351     [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:deleteSection] animated:YES];
352 }
353
354
355 #pragma mark -
356 #pragma mark Memory management
357
358 - (void)dealloc {
359     [account release];
360     [container release];
361     [containersViewController release];
362     [selectedContainerIndexPath release];
363     [rootFolderViewController release];
364     [activityIndicatorView release];
365     [super dealloc];
366 }
367
368 #pragma mark -
369 #pragma mark Helper functions
370
371 - (void)reloadMetadataSection {
372     NSString *activityMessage = @"Loading metadata...";
373     [activityIndicatorView release];
374     activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
375     [activityIndicatorView addToView:self.view scrollOffset:self.tableView.contentOffset.y]; 
376     [[self.account.manager getContainerInfo:container] 
377      success:^(OpenStackRequest *request) {
378          [activityIndicatorView removeFromSuperview];
379          container.metadata = [NSMutableDictionary dictionary];
380          for (NSString *header in request.responseHeaders) {
381              NSString *metadataKey;
382              NSString *metadataValue;
383              if ([header rangeOfString:@"X-Container-Meta-"].location != NSNotFound) {
384                  metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:17]];
385                  metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
386                  [container.metadata setObject:metadataValue forKey:metadataKey];
387              }
388          } 
389          NSIndexSet *metadataSections = [NSIndexSet indexSetWithIndex:kMetadata];
390          [self.tableView reloadSections:metadataSections withRowAnimation:UITableViewRowAnimationFade];
391      }
392      failure:^(OpenStackRequest *request) {
393          [activityIndicatorView removeFromSuperview];
394          [self alert:@"There was a problem retrieving the container's metadata." request:request]; 
395      }];
396 }
397
398 @end
399