2 // StorageObjectViewController.m
5 // Created by Mike Mayo on 12/19/10.
6 // The OpenStack project is provided under the Apache 2.0 license.
9 #import "StorageObjectViewController.h"
10 #import "OpenStackAccount.h"
11 #import "OpenStackRequest.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"
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"
37 #define maxMetadataViewableLength 12
39 // TODO: use etag to reset download
40 // TODO: try downloading directly to the file to save memory. don't use object.data
44 Full Path folder/whatever.txt
47 Last Modified 2012-12-21 11:11:00
53 else if fileDownloading:
56 Downloading (progress view)
63 Key Value -> tap goes to a metadata item VC to edit or delete
65 Add Metadata... (if max not already reached)
76 @implementation StorageObjectViewController
78 @synthesize account, container, folder, object, folderViewController;
79 @synthesize oldPublicURI, permissions, documentInteractionController, objectIsReadOnly, versionID;
81 #pragma mark - View lifecycle
83 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
84 return ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
90 objectIsPublicSwitch = [[UISwitch alloc] init];
91 [objectIsPublicSwitch addTarget:self action:@selector(objectIsPublicSwitchChanged:) forControlEvents:UIControlEventValueChanged];
93 self.permissions = [object permissions];
94 objectIsReadOnly = NO;
95 if (account.sharingAccount) {
96 if (permissions.count) {
97 objectIsReadOnly = ![[permissions objectForKey:account.username] isEqualToString:@"write"];
99 objectIsPublicSwitch.enabled = NO;
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];
107 fileDownloading = YES;
108 request.downloadProgressDelegate = self;
109 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
114 - (void)viewWillAppear:(BOOL)animated {
115 [super viewWillAppear:animated];
116 self.navigationItem.title = object.name;
118 NSIndexPath *selection = [self.tableView indexPathForSelectedRow];
120 [self.tableView deselectRowAtIndexPath:selection animated:YES];
122 OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
123 if ([app cacheFilePathForHash:object.hash]) {
124 fileDownloaded = YES;
129 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad) {
130 CGRect rect = downloadProgressView.frame;
131 rect.size.width = 440.0;
132 downloadProgressView.frame = rect;
135 publicLinkSection = 3;
136 permissionsSection = 4;
141 publicLinkSection = -1;
142 permissionsSection = -1;
143 versionsSection = -1;
145 objectIsReadOnly = YES;
147 if (account.sharingAccount || account.shared) {
148 versionsSection = -1;
152 objectIsPublic = ([object.publicURI length]);
153 objectIsPublicSwitch.on = objectIsPublic;
154 [self.tableView reloadData];
157 - (void)viewDidAppear:(BOOL)animated {
158 [super viewDidAppear:animated];
159 if (!object.metadata) {
160 [self reloadMetadataSection];
162 [self updatePermissionsUserCatalog];
164 if (fileDownloading) {
165 [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:kActions]
166 atScrollPosition:UITableViewScrollPositionMiddle
171 #pragma mark - Memory management
174 if (fileDownloading) {
175 OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
176 OpenStackRequest *request = [app objectDownloadRequestForAccount:account container:container object:object];
177 request.downloadProgressDelegate = nil;
180 [downloadProgressView release];
181 [cdnURLActionSheet release];
182 [folderViewController release];
183 [oldPublicURI release];
184 [permissions release];
185 [objectIsPublicSwitch release];
186 [documentInteractionController release];
191 #pragma mark - Internal
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:@"!*'();:@&=+$,?%#[]"]]];
203 return [account.provider.publicLinkURLPrefix URLByAppendingPathComponent:
204 [NSString encodeToPercentEscape:[self.object.publicURI substringFromIndex:1]
205 charactersToEncode:@"!*'();:@&=+$,?%#[]"]];
209 - (void)objectIsPublicSwitchChanged:(id)sender {
210 NSString *activityMessage = [NSString stringWithFormat:@"Enabling public link.."];
211 self.oldPublicURI = object.publicURI;
213 if (objectIsPublic) {
214 activityMessage = [NSString stringWithFormat:@"Disabling public link.."];
215 object.publicURI = @"";
217 object.publicURI = @"TRUE";
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];
235 failure:^(OpenStackRequest *request) {
236 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
237 [self alert:@"There was a problem retrieving the public link from the server." request:request];
240 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
241 [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
242 withRowAnimation:UITableViewRowAnimationTop];
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];
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;
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,
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);
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];
282 [self alert:@"Error" message:@"This file could not be opened."];
288 if (![MFMailComposeViewController canSendMail]) {
289 [self alert:@"Cannot send mail" message:@"Your device has not been configured for sending mail"];
291 OpenStackAppDelegate *app = [[UIApplication sharedApplication] delegate];
292 NSString *filePath = [app cacheFilePathForHash:object.hash];
293 NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
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];
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];
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];
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];
335 if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPhone) {
336 folderViewController.deletedObject = object;
337 [self.navigationController popViewControllerAnimated:YES];
339 [folderViewController deleteAnimatedObject:object];
340 [folderViewController setDetailViewController];
344 #pragma mark - Actions
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];
362 [self.tableView reloadData];
363 [self updatePermissionsUserCatalog];
365 failure:^(OpenStackRequest *request) {
366 [activityIndicatorView stopAnimatingAndRemoveFromSuperview];
367 [self alert:@"There was a problem retrieving the object's metadata." request:request];
371 - (void)downloadFileForAction:(StorageObjectAction)action {
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
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) {
385 fileDownloading = NO;
386 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
388 fileDownloaded = YES;
389 fileDownloading = NO;
390 if ([self.navigationController.visibleViewController isEqual:self]) {
391 if ([[request.userInfo objectForKey:@"action"] integerValue] == StorageObjectActionMailFile) {
397 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
400 failure:^(OpenStackRequest *request) {
402 fileDownloading = NO;
403 [self alert:@"File failed to download." request:request];
404 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
408 #pragma mark - UITableViewDataSource
410 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
411 int numberOfSections = 7;
412 if (publicLinkSection < 0)
414 if (permissionsSection < 0)
416 if (versionsSection < 0)
418 if (deleteSection < 0)
421 return numberOfSections;
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) {
430 } else if (fileDownloading) {
435 } else if (section == kMetadata) {
436 if (objectIsReadOnly) {
437 return [object.metadata count];
439 return ([object.metadata count] + 1);
441 } else if (section == publicLinkSection) {
442 return (objectIsPublic ? 2 : 1);
443 } else if (section == permissionsSection) {
444 if (account.sharingAccount || objectIsReadOnly) {
445 return permissions.count;
447 return (permissions.count + 1);
454 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
455 CGFloat result = aTableView.rowHeight;
456 if ((indexPath.section == kDetails) && ((indexPath.row == 0) || (indexPath.row == 1))) {
458 if (indexPath.row == 0) {
461 text = object.fullPath;
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;
475 return MAX(aTableView.rowHeight, result);
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];
483 cell = [[[TextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
484 cell.textView.frame = cell.contentView.frame;
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;
497 if (indexPath.section == deleteSection) {
498 static NSString *CellIdentifier = @"DeleteCell";
499 UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
501 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
502 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
503 cell.textLabel.textAlignment = UITextAlignmentCenter;
504 cell.textLabel.text = @"Delete Object";
509 static NSString *CellIdentifier = @"Cell";
510 UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
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;
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];
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;
545 } else if (indexPath.section == kMetadata) {
546 if (objectIsReadOnly) {
547 cell.accessoryType = UITableViewCellAccessoryNone;
548 cell.selectionStyle = UITableViewCellSelectionStyleNone;
549 cell.userInteractionEnabled = NO;
552 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
553 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
556 cell.accessoryView = nil;
557 if (indexPath.row == [object.metadata count]) {
558 cell.textLabel.text = @"Add Metadata";
559 cell.detailTextLabel.text = @"";
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:@"..."];
569 if ([metadataValueCellText length] > maxMetadataViewableLength) {
570 metadataValueCellText = [metadataValueCellText substringToIndex:(maxMetadataViewableLength - 3)];
571 metadataValueCellText = [metadataValueCellText stringByAppendingString:@"..."];
574 cell.textLabel.text = metadataKeyCellText;
575 cell.detailTextLabel.text = metadataValueCellText;
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;
585 } else if (indexPath.section == permissionsSection) {
586 if (account.sharingAccount) {
587 cell.accessoryType = UITableViewCellAccessoryNone;
588 cell.selectionStyle = UITableViewCellSelectionStyleNone;
589 cell.userInteractionEnabled = NO;
591 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
592 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
594 cell.accessoryView = nil;
596 if (indexPath.row == permissions.count) {
597 cell.textLabel.text = @"Share";
598 cell.detailTextLabel.text = @"";
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]];
606 [displayname appendFormat:@":%@", group];
608 cell.textLabel.text = displayname;
610 cell.detailTextLabel.numberOfLines = 1;
611 cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
612 cell.detailTextLabel.text = ([[permissions objectForKey:user] isEqualToString:@"write"] ? @"Read/Write" : @"Read Only");
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;
623 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
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;
633 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
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;
647 } else if (indexPath.row == 3) {
648 if (fileDownloading) {
649 cell.textLabel.text = @"Cancel";
650 cell.accessoryType = UITableViewCellAccessoryNone;
651 cell.detailTextLabel.text = @"";
654 } else if (indexPath.section == versionsSection) {
655 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
656 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
657 cell.textLabel.text = @"Versions";
658 cell.detailTextLabel.text = @"";
664 #pragma mark - UITableViewDelegate
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;
672 if (indexPath.row == [self.object.metadata count]) {
675 vc.removeMetadataEnabled = FALSE;
676 vc.navigationItem.title = @"Add Metadata";
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";
684 vc.metadataKey = metadataKey;
685 vc.metadataValue = metadataValue;
686 vc.account = account;
687 vc.container = container;
689 [self.navigationController pushViewController:vc animated:YES];
691 } else if (indexPath.section == permissionsSection) {
692 EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
695 if (indexPath.row == permissions.count) {
697 vc.removePermissionsEnabled = NO;
698 vc.navigationItem.title = @"Add Permission";
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;
705 vc.readPermissionSelected = NO;
707 if ([userPermissions rangeOfString:@"write"].location != NSNotFound) {
708 vc.writePermissionSelected = YES;
710 vc.writePermissionSelected = NO;
712 vc.removePermissionsEnabled = YES;
713 vc.navigationItem.title = @"Edit Permission";
716 vc.permissionUser = user;
717 vc.permissions = permissions;
718 vc.account = account;
719 vc.container = container;
721 vc.folderViewController = folderViewController;
722 [self.navigationController pushViewController:vc animated:YES];
724 } else if (indexPath.section == kActions) {
725 if (indexPath.row == 0) {
726 if (fileDownloaded) {
728 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
729 } else if (!fileDownloading) {
730 [self downloadFileForAction:StorageObjectActionOpenFile];
731 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
733 } else if (indexPath.row == 1) {
734 if (fileDownloaded) {
736 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
737 } else if (!fileDownloading) {
738 [self downloadFileForAction:StorageObjectActionMailFile];
739 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
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];
747 fileDownloading = NO;
748 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
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];
755 request.downloadProgressDelegate = nil;
758 fileDownloading = NO;
759 [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:kActions] withRowAnimation:UITableViewRowAnimationNone];
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."
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;
774 [self.navigationController pushViewController:vc animated:YES];
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];
788 #pragma mark - UIDocumentInteractionControllerDelegate
790 - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *) controller {
791 return self.navigationController;
794 #pragma mark - UIActionSheetDelegate
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];
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) {
812 [self alert:@"There was a problem deleting this file." request:request];
816 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:deleteSection];
817 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
820 #pragma mark - MFMailComposeViewControllerDelegate
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];