Update version
[pithos-ios] / Classes / StorageObjectViewController.m
1 //
2 //  StorageObjectViewController.m
3 //  OpenStack
4 //
5 //  Created by Mike Mayo on 12/19/10.
6 //  The OpenStack project is provided under the Apache 2.0 license.
7 //
8
9 #import "StorageObjectViewController.h"
10 #import "OpenStackAccount.h"
11 #import "OpenStackRequest.h"
12 #import "Container.h"
13 #import "Folder.h"
14 #import "StorageObject.h"
15 #import "AccountManager.h"
16 #import "AnimatedProgressView.h"
17 #import "UIViewController+Conveniences.h"
18 #import "MediaViewController.h"
19 #import "FolderViewController.h"
20 #import "UIColor+MoreColors.h"
21 #import "OpenStackAppDelegate.h"
22 #import "EditMetadataViewController.h"
23 #import "Provider.h"
24 #import "EditPermissionsViewController.h"
25 #import "TextViewCell.h"
26 #import "NSString+Conveniences.h"
27 #import "APICallback.h"
28 #import "ObjectVersionsViewController.h"
29 #import <MediaPlayer/MPMoviePlayerController.h>
30 #import <MessageUI/MessageUI.h>
31 #import "ActivityIndicatorView.h"
32
33 #define kDetails 0
34 #define kActions 1
35 #define kMetadata 2
36
37 #define maxMetadataViewableLength 12
38
39 // TODO: use etag to reset download
40 // TODO: try downloading directly to the file to save memory.  don't use object.data
41
42 /*
43 Name                            whatever.txt
44 Full Path                       folder/whatever.txt
45 Size                            123 KB
46 Type                            text/plain
47 Last Modified                   2012-12-21 11:11:00
48
49 if fileDownloaded:
50  Open File
51  Mail File
52  Delete from Cache
53 else if fileDownloading:
54  Open File (disabled)
55  Mail File (disabled)
56  Downloading (progress view)
57  Cancel
58 else:
59  Open File
60  Mail File
61
62 Metadata
63 Key                             Value -> tap goes to a metadata item VC to edit or delete
64 Key                             Value
65 Add Metadata... (if max not already reached)
66
67 Public URL                      On/Off
68
69 Share
70  
71 Versions
72  
73 Delete Object
74 */
75
76 @implementation StorageObjectViewController
77
78 @synthesize account, container, folder, object, folderViewController;
79 @synthesize oldPublicURI, permissions, documentInteractionController, objectIsReadOnly, versionID;
80
81 #pragma mark - View lifecycle
82
83 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
84     return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
85 }
86
87 - (void)viewDidLoad {
88     [super viewDidLoad];
89     
90     objectIsPublicSwitch = [[UISwitch alloc] init];
91     [objectIsPublicSwitch addTarget:self action:@selector(objectIsPublicSwitchChanged:) forControlEvents:UIControlEventValueChanged];
92     
93     self.permissions = [object permissions];
94     objectIsReadOnly = NO;
95     if (account.sharingAccount) { 
96         if (permissions.count) {
97             objectIsReadOnly = ![[permissions objectForKey:account.username] isEqualToString:@"write"];
98         }
99         objectIsPublicSwitch.enabled = NO;
100     }
101     
102     downloadProgressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
103     fileDownloading = NO;
104     OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
105     OpenStackRequest *request = [app objectDownloadRequestForAccount:account container:container object:object];
106     if (request) {
107         fileDownloading = YES;
108         request.downloadProgressDelegate = self;
109         [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
110     }
111     
112 }
113
114 - (void)viewWillAppear:(BOOL)animated {
115     [super viewWillAppear:animated];
116     self.navigationItem.title = object.name;
117     
118     NSIndexPath *selection = [self.tableView indexPathForSelectedRow];
119         if (selection)
120                 [self.tableView deselectRowAtIndexPath:selection animated:YES];
121     
122     OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
123     if ([app cacheFilePathForHash:object.hash]) {
124         fileDownloaded = YES;
125     } else {
126         fileDownloaded = NO;
127     }
128     
129     if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
130         CGRect rect = downloadProgressView.frame;
131         rect.size.width = 440.0;
132         downloadProgressView.frame = rect;
133     }
134     
135     publicLinkSection = 3;
136     permissionsSection = 4;
137     versionsSection = 5;
138     deleteSection = 6;
139     
140     if (versionID) {
141         publicLinkSection = -1;
142         permissionsSection = -1;
143         versionsSection = -1;
144         deleteSection = -1;
145         objectIsReadOnly = YES;
146     }
147     if (account.sharingAccount || account.shared) {
148         versionsSection = -1;
149         deleteSection = -1;
150     }
151     
152     objectIsPublic = ([object.publicURI length]);
153     objectIsPublicSwitch.on = objectIsPublic;
154     [self.tableView reloadData];
155 }
156
157 - (void)viewDidAppear:(BOOL)animated {
158     [super viewDidAppear:animated];
159     if (!object.metadata) {
160         [self reloadMetadataSection];
161     } else {
162         [self updatePermissionsUserCatalog];
163     }
164     if (fileDownloading) {
165         [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:kActions]
166                               atScrollPosition:UITableViewScrollPositionMiddle
167                                       animated:YES];
168     }
169 }
170
171 #pragma mark - Memory management
172
173 - (void)dealloc {
174     if (fileDownloading) {
175         OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
176         OpenStackRequest *request = [app objectDownloadRequestForAccount:account container:container object:object];
177         request.downloadProgressDelegate = nil;
178     }
179     [account release];
180     [downloadProgressView release];
181     [cdnURLActionSheet release];
182     [folderViewController release];
183     [oldPublicURI release];
184     [permissions release];
185     [objectIsPublicSwitch release];
186     [documentInteractionController release];
187     [versionID release];
188     [super dealloc];
189 }
190
191 #pragma mark - Internal
192
193 - (NSURL *)publicLinkURL {
194     if ([self.object.publicURI hasPrefix:@"https://"]) {
195         return [NSURL URLWithString:[NSString stringWithFormat:@"https://%@",
196                                      [NSString encodeToPercentEscape:[self.object.publicURI substringFromIndex:8]
197                                                   charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
198     } else if ([self.object.publicURI hasPrefix:@"http://"]) {
199         return [NSURL URLWithString:[NSString stringWithFormat:@"http://%@",
200                                      [NSString encodeToPercentEscape:[self.object.publicURI substringFromIndex:7]
201                                                   charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];
202     } else {
203         return [account.provider.publicLinkURLPrefix URLByAppendingPathComponent:
204                 [NSString encodeToPercentEscape:[self.object.publicURI substringFromIndex:1]
205                              charactersToEncode:@"!*'();:@&=+$,?%#[]"]];
206     }
207 }
208
209 - (void)objectIsPublicSwitchChanged:(id)sender {
210     NSString *activityMessage = [NSString stringWithFormat:@"Enabling public link.."];
211     self.oldPublicURI = object.publicURI;
212     
213     if (objectIsPublic) {
214         activityMessage = [NSString stringWithFormat:@"Disabling public link.."];
215         object.publicURI = @"";
216     } else {
217         object.publicURI = @"TRUE";
218     }
219     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:activityMessage
220                                                                                                    andAddToView:self.view
221                                                                                                    scrollOffset:self.tableView.contentOffset.y];
222     objectIsPublic = !objectIsPublic;
223     [[self.account.manager writeObjectMetadata:container object:object]
224      success:^(OpenStackRequest *request) {
225          NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:publicLinkSection];
226          if (objectIsPublic) {
227              [[self.account.manager getObjectInfo:container object:object version:versionID]
228               success:^(OpenStackRequest *request) {
229                   [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
230                   object.publicURI = [request.responseHeaders objectForKey:@"X-Object-Public"];
231                   NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:publicLinkSection];
232                   [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
233                                         withRowAnimation:UITableViewRowAnimationBottom];
234               }
235               failure:^(OpenStackRequest *request) {
236                   [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
237                   [self alert:@"There was a problem retrieving the public link from the server." request:request];
238               }];
239          } else {
240              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
241              [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
242                                    withRowAnimation:UITableViewRowAnimationTop];
243          }
244      }
245      failure:^(OpenStackRequest *request) {
246          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
247          objectIsPublic = !objectIsPublic;
248          objectIsPublicSwitch.on = !objectIsPublicSwitch.on;
249          object.publicURI = oldPublicURI;
250          [self alert:@"There was a problem enabling the public link." request:request];
251      }];
252 }
253
254 - (void)openFile {
255     OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
256     NSString *filePath = [app cacheFilePathForHash:object.hash];
257     self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
258     self.documentInteractionController.delegate = self;
259     self.documentInteractionController.name = object.name;
260     
261     CGRect frameToPresentMenuFrom;
262     if ([[UIDevice currentDevice].systemVersion hasPrefix:@"6"]) {
263         frameToPresentMenuFrom = CGRectMake(0.0,
264                                             self.tableView.contentOffset.y,
265                                             self.view.frame.size.width,
266                                             1.0);
267     } else {
268         UITableViewCell *openFileCell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:kActions]];
269         frameToPresentMenuFrom = CGRectMake(openFileCell.frame.origin.x,
270                                             openFileCell.frame.origin.y - self.tableView.contentOffset.y,
271                                             openFileCell.frame.size.width,
272                                             openFileCell.frame.size.height);
273     }
274     if (![self.documentInteractionController presentOptionsMenuFromRect:frameToPresentMenuFrom inView:self.view animated:YES]) {
275         if ([self.object isPlayableMedia]) {
276             MediaViewController *vc = [[MediaViewController alloc] initWithNibName:@"MediaViewController" bundle:nil];
277             vc.container = self.container;
278             vc.object = self.object;
279             [self.navigationController pushViewController:vc animated:YES];
280             [vc release];
281         } else {
282             [self alert:@"Error" message:@"This file could not be opened."];
283         }
284     }
285 }
286
287 - (void)mailFile {
288     if (![MFMailComposeViewController canSendMail]) {
289         [self alert:@"Cannot send mail" message:@"Your device has not been configured for sending mail"];
290     } else {
291         OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
292         NSString *filePath = [app cacheFilePathForHash:object.hash];
293         NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
294         
295         MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
296         vc.mailComposeDelegate = self;
297         [vc setSubject:object.name];
298         [vc addAttachmentData:data mimeType:object.contentType fileName:object.name];
299         [vc setMessageBody:@"" isHTML:NO];
300         if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
301             vc.modalPresentationStyle = UIModalPresentationPageSheet;
302         [self presentModalViewController:vc animated:YES];
303         [vc release];
304     }
305 }
306
307 - (void)updatePermissionsUserCatalog {
308     NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:permissions.count];
309     for (NSString *user in permissions) {
310         NSRange rangeOfColumn = [user rangeOfString:@":"];
311         NSString *UUID = (rangeOfColumn.location == NSNotFound) ? user : [user substringToIndex:rangeOfColumn.location];
312         if (![UUID isEqualToString:@"*"]) {
313             [UUIDs addObject:UUID];
314         }
315     }
316     if (UUIDs.count) {
317         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading permissions..."
318                                                                                                        andAddToView:self.view];
319         [[self.account.manager userCatalogForDisplaynames:nil UUIDs:UUIDs]
320          success:^(OpenStackRequest *request) {
321              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
322              [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:permissionsSection] withRowAnimation:UITableViewRowAnimationNone];
323          }
324          failure:^(OpenStackRequest *request) {
325              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
326              if (request.responseStatusCode != 404) {
327                  // Don't show alert on 404, since it can be a pre-UUID server.
328                  [self alert:@"Failed to translate sharing UUIDs." request:request];
329              }
330          }];
331     }
332 }
333
334 - (void)delete {
335     if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
336         folderViewController.deletedObject = object;
337         [self.navigationController popViewControllerAnimated:YES];
338     } else {
339         [folderViewController deleteAnimatedObject:object];
340         [folderViewController setDetailViewController];
341     }
342 }
343
344 #pragma mark - Actions
345
346 - (void)reloadMetadataSection {
347     __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Loading metadata..."
348                                                                                                    andAddToView:self.view];
349     [[self.account.manager getObjectInfo:container object:object version:versionID]
350      success:^(OpenStackRequest *request) {
351          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
352          object.metadata = [NSMutableDictionary dictionary];
353          for (NSString *header in request.responseHeaders) {
354              NSString *metadataKey;
355              NSString *metadataValue;
356              if ([header rangeOfString:@"X-Object-Meta-"].location != NSNotFound) {
357                  metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:14]];
358                  metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
359                  [object.metadata setObject:metadataValue forKey:metadataKey];
360              }
361          }
362          [self.tableView reloadData];
363          [self updatePermissionsUserCatalog];
364      }
365      failure:^(OpenStackRequest *request) {
366          [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
367          [self alert:@"There was a problem retrieving the object's metadata." request:request];
368      }];
369 }
370
371 - (void)downloadFileForAction:(StorageObjectAction)action {
372     if (fileDownloading)
373         return;
374     fileDownloading = YES;
375     [downloadProgressView setProgress:0.0 animated:NO];
376     [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
377     [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:kActions]
378                           atScrollPosition:UITableViewScrollPositionMiddle
379                                   animated:YES];
380     NSMutableDictionary *requestUserInfo = [NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:action] forKey:@"action"];
381     [[self.account.manager getObject:self.container object:self.object downloadProgressDelegate:self requestUserInfo:requestUserInfo version:versionID]
382      success:^(OpenStackRequest *request) {
383          if (request.isCancelled) {
384              fileDownloaded = NO;
385              fileDownloading = NO;
386              [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
387          } else {
388              fileDownloaded = YES;
389              fileDownloading = NO;
390              if ([self.navigationController.visibleViewController isEqual:self]) {
391                  if ([[request.userInfo objectForKey:@"action"] integerValue] == StorageObjectActionMailFile) {
392                      [self mailFile];
393                  } else {
394                      [self openFile];
395                  }
396              }
397              [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
398          }
399      }
400      failure:^(OpenStackRequest *request) {
401          fileDownloaded = NO;
402          fileDownloading = NO;
403          [self alert:@"File failed to download." request:request];
404          [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
405      }];
406 }
407
408 #pragma mark - UITableViewDataSource
409
410 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
411     int numberOfSections = 7;
412     if (publicLinkSection < 0)
413         numberOfSections--;
414     if (permissionsSection < 0)
415         numberOfSections--;
416     if (versionsSection < 0)
417         numberOfSections--;
418     if (deleteSection < 0)
419         numberOfSections--;
420
421     return numberOfSections;
422 }
423
424 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
425     if (section == kDetails) {
426         return (object.lastModifiedString ? 5: 4);
427     } else if (section == kActions) {
428         if (fileDownloaded) {
429             return 3;
430         } else if (fileDownloading) {
431             return 4;
432         } else {
433             return 2;
434         }
435     } else if (section == kMetadata) {
436         if (objectIsReadOnly) {
437             return [object.metadata count];
438         } else {
439             return ([object.metadata count] + 1);
440         }
441     } else if (section == publicLinkSection) {
442         return (objectIsPublic ? 2 : 1);
443     } else if (section == permissionsSection) {
444         if (account.sharingAccount || objectIsReadOnly) {
445             return permissions.count;
446         } else {
447             return (permissions.count + 1);
448         }
449     } else {
450         return 1;
451     }
452 }
453
454 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
455     CGFloat result = aTableView.rowHeight;
456     if ((indexPath.section == kDetails) && ((indexPath.row == 0) || (indexPath.row == 1))) {
457         NSString *text;
458         if (indexPath.row == 0) {
459             text = object.name;
460         } else {
461             text = object.fullPath;
462         }
463         result = 22.0 + [text sizeWithFont:[UIFont systemFontOfSize:18.0]
464                          constrainedToSize:(([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ?
465                                             CGSizeMake(537.0, 9000.0) :
466                                             CGSizeMake(221.0, 9000.0))
467                              lineBreakMode:UILineBreakModeCharacterWrap].height;
468     } else if ((indexPath.section == publicLinkSection) && (indexPath.row == 1)) {
469         result = 30.0 + [[[self publicLinkURL] description] sizeWithFont:[UIFont systemFontOfSize:18.0]
470                                                        constrainedToSize:(([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) ?
471                                                                           CGSizeMake(596.0, 9000.0) :
472                                                                           CGSizeMake(280.0, 9000.0))
473                                                            lineBreakMode:UILineBreakModeCharacterWrap].height;
474     }
475     return MAX(aTableView.rowHeight, result);
476 }
477
478 - (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
479     if ((indexPath.section == publicLinkSection) && (indexPath.row == 1)) {
480         static NSString *CellIdentifier = @"TextViewCell";
481         TextViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
482         if (cell == nil) {
483             cell = [[[TextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
484             cell.textView.frame = cell.contentView.frame;
485
486         }
487         cell.selectionStyle = UITableViewCellSelectionStyleNone;
488         cell.textView.backgroundColor = [UIColor clearColor];
489         cell.textView.font = [UIFont systemFontOfSize:15.0];
490         cell.textView.dataDetectorTypes = UIDataDetectorTypeLink;
491         cell.textView.text = [[self publicLinkURL] description];
492         cell.selectionStyle = UITableViewCellSelectionStyleNone;
493         cell.accessoryView = UITableViewCellAccessoryNone;
494         return cell;
495     }
496     
497     if (indexPath.section == deleteSection) {
498         static NSString *CellIdentifier = @"DeleteCell";
499         UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
500         if (cell == nil) {
501             cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
502             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
503             cell.textLabel.textAlignment = UITextAlignmentCenter;
504             cell.textLabel.text = @"Delete Object";
505         }
506         return cell;
507     }
508     
509     static NSString *CellIdentifier = @"Cell";
510     UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
511     if (cell == nil) {
512         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
513         cell.textLabel.backgroundColor = [UIColor clearColor];
514         cell.detailTextLabel.backgroundColor = [UIColor clearColor];
515         cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
516     }
517     cell.textLabel.textColor = [UIColor blackColor];
518     cell.detailTextLabel.numberOfLines = 0;
519     cell.userInteractionEnabled = YES;
520     cell.detailTextLabel.textAlignment = UITextAlignmentRight;
521     cell.accessoryView = nil;
522     cell.textLabel.font = [UIFont boldSystemFontOfSize:17.0];
523     
524     // Configure the cell...
525     if (indexPath.section == kDetails) {
526         cell.accessoryType = UITableViewCellAccessoryNone;
527         cell.selectionStyle = UITableViewCellSelectionStyleNone;
528         cell.accessoryView = nil;
529         if (indexPath.row == 0) {
530             cell.textLabel.text = @"Name";
531             cell.detailTextLabel.text = object.name;
532         } else if (indexPath.row == 1) {
533             cell.textLabel.text = @"Full Path";
534             cell.detailTextLabel.text = object.fullPath;
535         } else if (indexPath.row == 2) {
536             cell.textLabel.text = @"Size";
537             cell.detailTextLabel.text = [object humanizedBytes];
538         } else if (indexPath.row == 3) {
539             cell.textLabel.text = @"Type";
540             cell.detailTextLabel.text = object.contentType;
541         } else if (indexPath.row == 4) {
542             cell.textLabel.text = @"Last Modified";
543             cell.detailTextLabel.text = object.lastModifiedString;
544         }
545     } else if (indexPath.section == kMetadata) {
546         if (objectIsReadOnly) {
547             cell.accessoryType = UITableViewCellAccessoryNone;
548             cell.selectionStyle = UITableViewCellSelectionStyleNone;
549             cell.userInteractionEnabled = NO;
550         }
551         else {
552             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
553             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
554         }
555         
556         cell.accessoryView = nil;
557         if (indexPath.row == [object.metadata count]) {
558             cell.textLabel.text = @"Add Metadata";
559             cell.detailTextLabel.text = @"";
560         } else {
561             NSString *key = [[object.metadata allKeys] objectAtIndex:indexPath.row];
562             NSString *value = [object.metadata objectForKey:key];
563             NSString *metadataKeyCellText = key;
564             NSString *metadataValueCellText = value;
565             if ([metadataKeyCellText length] > maxMetadataViewableLength) {
566                 metadataKeyCellText = [metadataKeyCellText substringToIndex:(maxMetadataViewableLength - 3)];
567                 metadataKeyCellText = [metadataKeyCellText stringByAppendingString:@"..."];
568             }
569             if ([metadataValueCellText length] > maxMetadataViewableLength) {
570                 metadataValueCellText = [metadataValueCellText substringToIndex:(maxMetadataViewableLength - 3)];
571                 metadataValueCellText = [metadataValueCellText stringByAppendingString:@"..."];
572             }
573             
574             cell.textLabel.text = metadataKeyCellText;
575             cell.detailTextLabel.text = metadataValueCellText;
576         }
577     } else if (indexPath.section == publicLinkSection) {
578         if (indexPath.row == 0) {
579             cell.textLabel.text = @"Public URL";        
580             cell.detailTextLabel.text = @"";
581             cell.accessoryView = objectIsPublicSwitch;
582             cell.accessoryType = UITableViewCellAccessoryNone;
583             cell.selectionStyle = UITableViewCellSelectionStyleNone;
584         }
585     } else if (indexPath.section == permissionsSection) {
586         if (account.sharingAccount) {
587             cell.accessoryType = UITableViewCellAccessoryNone;
588             cell.selectionStyle = UITableViewCellSelectionStyleNone;
589             cell.userInteractionEnabled = NO;
590         } else {
591             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
592             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
593         }
594         cell.accessoryView = nil;
595         
596         if (indexPath.row == permissions.count) {
597             cell.textLabel.text = @"Share";
598             cell.detailTextLabel.text = @""; 
599         } else {
600             NSString *user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
601             NSRange rangeOfColumn = [user rangeOfString:@":"];
602             NSString *UUID = (rangeOfColumn.location == NSNotFound) ? user : [user substringToIndex:rangeOfColumn.location];
603             NSString *group = ((rangeOfColumn.location == NSNotFound) || (rangeOfColumn.location == user.length - 1)) ? nil : [user substringFromIndex:(rangeOfColumn.location + 1)];
604             NSMutableString *displayname = [NSMutableString stringWithString:[account displaynameForUUID:UUID safe:YES]];
605             if (group) {
606                 [displayname appendFormat:@":%@", group];
607             }
608             cell.textLabel.text = displayname;
609             
610             cell.detailTextLabel.numberOfLines = 1;
611             cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
612             cell.detailTextLabel.text = ([[permissions objectForKey:user] isEqualToString:@"write"] ? @"Read/Write" : @"Read Only");
613         }
614     } else if (indexPath.section == kActions) {
615         if (indexPath.row == 0) {
616             cell.textLabel.text = @"Open File";
617             cell.accessoryType = UITableViewCellAccessoryNone;
618             cell.detailTextLabel.text = @"";
619             if (fileDownloading) {
620                 cell.textLabel.textColor = [UIColor grayColor];
621                 cell.selectionStyle = UITableViewCellSelectionStyleNone;
622             } else {
623                 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
624             }
625         } else if (indexPath.row == 1) {
626             cell.textLabel.text = @"Email File as Attachment";
627             cell.detailTextLabel.text = @"";
628             cell.accessoryType = UITableViewCellAccessoryNone;
629             if (fileDownloading) {
630                 cell.textLabel.textColor = [UIColor grayColor];
631                 cell.selectionStyle = UITableViewCellSelectionStyleNone;
632             } else {
633                 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
634             }
635         } else if (indexPath.row == 2) {
636             if (fileDownloaded) {
637                 cell.textLabel.text = @"Remove from Cache";
638                 cell.accessoryType = UITableViewCellAccessoryNone;
639                 cell.detailTextLabel.text = @"";
640             } else if (fileDownloading) {
641                 cell.textLabel.text = @"Downloading";
642                 cell.accessoryType = UITableViewCellAccessoryNone;
643                 cell.detailTextLabel.text = @"";
644                 cell.selectionStyle = UITableViewCellSelectionStyleNone;
645                 cell.accessoryView = downloadProgressView;
646             }
647         } else if (indexPath.row == 3) {
648             if (fileDownloading) {
649                 cell.textLabel.text = @"Cancel";
650                 cell.accessoryType = UITableViewCellAccessoryNone;
651                 cell.detailTextLabel.text = @"";
652             }
653         }
654     } else if (indexPath.section == versionsSection) {
655         cell.selectionStyle = UITableViewCellSelectionStyleBlue;
656         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
657         cell.textLabel.text = @"Versions";
658         cell.detailTextLabel.text = @"";
659     }
660     
661     return cell;
662 }
663
664 #pragma mark - UITableViewDelegate
665
666 - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
667     if (indexPath.section == kMetadata) {
668         EditMetadataViewController *vc = [[EditMetadataViewController alloc] initWithNibName:@"EditMetadataViewController" bundle:nil];
669         NSString *metadataKey;
670         NSString *metadataValue;
671         
672         if (indexPath.row == [self.object.metadata count]) {
673             metadataKey = @"";
674             metadataValue = @"";
675             vc.removeMetadataEnabled = FALSE;
676             vc.navigationItem.title = @"Add Metadata";
677         } else {
678             metadataKey = [[self.object.metadata allKeys] objectAtIndex:indexPath.row];
679             metadataValue = [self.object.metadata objectForKey:metadataKey];
680             vc.removeMetadataEnabled = YES;
681             vc.navigationItem.title = @"Edit Metadata";
682         }
683
684         vc.metadataKey = metadataKey;
685         vc.metadataValue = metadataValue;
686         vc.account = account;
687         vc.container = container;
688         vc.object = object;
689         [self.navigationController pushViewController:vc animated:YES];
690         [vc release];
691     } else if (indexPath.section == permissionsSection) {
692         EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
693         NSString *user;
694         
695         if (indexPath.row == permissions.count) {
696             user = @"";
697             vc.removePermissionsEnabled = NO;
698             vc.navigationItem.title = @"Add Permission";
699         } else {
700             user = [[[permissions allKeys] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)] objectAtIndex:indexPath.row];
701             NSString *userPermissions = [permissions objectForKey:user];
702             if ([userPermissions rangeOfString:@"read"].location != NSNotFound) {
703                 vc.readPermissionSelected = YES;
704             } else {
705                 vc.readPermissionSelected = NO;
706             }
707             if ([userPermissions rangeOfString:@"write"].location != NSNotFound) {
708                 vc.writePermissionSelected = YES;
709             } else {
710                 vc.writePermissionSelected = NO;
711             }
712             vc.removePermissionsEnabled = YES;
713             vc.navigationItem.title = @"Edit Permission";
714         }
715         
716         vc.permissionUser = user;
717         vc.permissions = permissions;
718         vc.account = account;
719         vc.container = container;
720         vc.object = object;
721         vc.folderViewController = folderViewController;
722         [self.navigationController pushViewController:vc animated:YES];
723         [vc release];
724     } else if (indexPath.section == kActions) {
725         if (indexPath.row == 0) {
726             if (fileDownloaded) {
727                 [self openFile];
728                 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
729             } else if (!fileDownloading) {
730                 [self downloadFileForAction:StorageObjectActionOpenFile];
731                 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
732             }
733         } else if (indexPath.row == 1) {
734             if (fileDownloaded) {
735                 [self mailFile];
736                 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
737             } else if (!fileDownloading) {
738                 [self downloadFileForAction:StorageObjectActionMailFile];
739                 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
740             }
741         } else if ((indexPath.row == 2) && fileDownloaded) {
742             OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
743             BOOL removed = [app removeCacheObjectForHash:object.hash];
744             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
745             if (removed) {
746                 fileDownloaded = NO;
747                 fileDownloading = NO;
748                 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
749             }
750         } else if ((indexPath.row == 3) && fileDownloading) {
751             OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
752             OpenStackRequest *request = [app objectDownloadRequestForAccount:account container:container object:object];
753             [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
754             if (request) {
755                 request.downloadProgressDelegate = nil;
756                 [request cancel];
757                 fileDownloaded = NO;
758                 fileDownloading = NO;
759                 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
760             }
761         }
762     } else if (indexPath.section == deleteSection) {
763         UIActionSheet *deleteActionSheet = [[[UIActionSheet alloc] initWithTitle:@"Are you sure you want to delete this file? This operation cannot be undone."
764                                                                         delegate:self
765                                                                cancelButtonTitle:@"Cancel"
766                                                           destructiveButtonTitle:@"Delete File"
767                                                                otherButtonTitles:nil] autorelease];
768         [deleteActionSheet showInView:self.view];
769     } else if (indexPath.section == versionsSection) {
770         ObjectVersionsViewController *vc = [[ObjectVersionsViewController alloc] initWithNibName:@"ObjectVersionsViewController" bundle:nil];
771         vc.account = account;
772         vc.container = container;
773         vc.object = object;
774         [self.navigationController pushViewController:vc animated:YES];
775         [vc release];
776     }
777 }
778
779 - (void)setProgress:(float)newProgress {
780     [downloadProgressView setProgress:newProgress animated:YES];    
781     if (newProgress >= 1.0) {
782         fileDownloading = NO;
783         fileDownloaded = YES;
784         [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
785     }
786 }
787
788 #pragma mark - UIDocumentInteractionControllerDelegate
789
790 - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *) controller {
791     return self.navigationController;
792 }
793
794 #pragma mark - UIActionSheetDelegate
795
796 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
797     if (buttonIndex == 0) {
798         // delete the file and pop out
799         __block ActivityIndicatorView *activityIndicatorView = [ActivityIndicatorView activityIndicatorViewWithText:@"Deleting file..."
800                                                                                                        andAddToView:self.view];
801         [[self.account.manager deleteObject:self.container object:self.object] 
802          success:^(OpenStackRequest *request) {
803              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
804              [self delete];
805          }
806          failure:^(OpenStackRequest *request) {
807              [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
808              // 404 Not Found means it's not there, so we can show the user that it's deleted
809              if (request.responseStatusCode == 404) {
810                  [self delete];
811              } else {
812                   [self alert:@"There was a problem deleting this file." request:request];
813              }
814          }];
815     }
816     NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:deleteSection];
817     [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
818 }
819
820 #pragma mark - MFMailComposeViewControllerDelegate
821
822 // Dismisses the email composition interface when users tap Cancel or Send.
823 - (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
824         [self dismissModalViewControllerAnimated:YES];
825 }
826
827 @end
828