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"
29 #define kPermissions 3
31 #define maxMetadataViewableLength 12
34 // TODO: use etag to reset download
35 // TODO: try downloading directly to the file to save memory. don't use object.data
41 Content Type text/plain
44 Key Value -> tap goes to a metadata item VC to edit or delete
46 Add Metadata... (if max not already reached)
48 CDN URL sections (action sheet to copy, open in safari, and email link)
50 Download File (if downloaded, Open File and Mail File as Attachment)
51 "After you download the file, you'll be able to attempt to open it and mail is as an attachment."
56 @implementation StorageObjectViewController
58 @synthesize account, container, folder, object, tableView, folderViewController;
59 @synthesize oldPubicURI, documentInteractionController;
61 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
62 return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
65 - (void)setBackgroundView {
67 UIImageView *logo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cloudfiles-large.png"]];
69 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
70 UIView *backgroundContainer = [[UIView alloc] init];
71 backgroundContainer.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
72 backgroundContainer.backgroundColor = [UIColor iPadTableBackgroundColor];
73 logo.contentMode = UIViewContentModeScaleAspectFit;
74 logo.frame = CGRectMake(100.0, 100.0, 1000.0, 1000.0);
76 [backgroundContainer addSubview:logo];
77 tableView.backgroundView = backgroundContainer;
78 [backgroundContainer release];
80 self.tableView.backgroundView = nil;
85 - (IBAction)homeButtonPressed:(id)sender {
86 [self.navigationController popToRootViewControllerAnimated:YES];
90 #pragma mark View lifecycle
94 objectIsPublicSwitch = [[UISwitch alloc] init];
95 [objectIsPublicSwitch addTarget:self action:@selector(objectIsPublicSwitchChanged:) forControlEvents:UIControlEventValueChanged];
97 deleteActionSheet = [[UIActionSheet alloc] initWithTitle:@"Are you sure you want to delete this file? This operation cannot be undone." delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:@"Delete File" otherButtonTitles:nil];
99 permissions = [[NSMutableDictionary alloc] init];
100 NSArray *sharingArray = [object.sharing componentsSeparatedByString:@";"];
101 for (NSString *typeSpecificPermissions in sharingArray) {
102 NSArray *array=[typeSpecificPermissions componentsSeparatedByString:@"="];
103 NSString *permissionsType = [array objectAtIndex:0];
104 if ([permissionsType hasPrefix:@" "])
105 permissionsType = [permissionsType substringFromIndex:1];
107 NSArray *users = [[array objectAtIndex:1] componentsSeparatedByString:@","];
108 for (NSString *user in users) {
109 [permissions setObject:permissionsType forKey:user];
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 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
123 NSString *documentsDirectory = [paths objectAtIndex:0];
124 NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
125 NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
127 NSFileManager *fileManager = [NSFileManager defaultManager];
128 fileDownloaded = [fileManager fileExistsAtPath:filePath];
130 downloadProgressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
132 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
133 CGRect rect = downloadProgressView.frame;
134 rect.size.width = 440.0;
135 downloadProgressView.frame = rect;
138 //[self setBackgroundView];
139 if (self.container.cdnEnabled) {
149 // let's see if we can tweet
150 UIApplication *app = [UIApplication sharedApplication];
151 NSURL *twitterURL = [NSURL URLWithString:@"twitter://post?message=test"];
153 if ([app canOpenURL:twitterURL]) {
154 cdnURLActionSheet = [[UIActionSheet alloc] initWithTitle:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Copy to Pasteboard", @"Open in Safari", @"Email Link to File", @"Tweet Link to File", nil];
156 cdnURLActionSheet = [[UIActionSheet alloc] initWithTitle:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] delegate:self cancelButtonTitle:@"Cancel" destructiveButtonTitle:nil otherButtonTitles:@"Copy to Pasteboard", @"Open in Safari", @"Email Link to File", nil];
158 objectIsPublic = ([object.publicURI length]);
159 objectIsPublicSwitch.on = objectIsPublic;
160 [self.tableView reloadData];
164 #pragma mark Table view data source
166 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
167 return self.container.cdnEnabled ? 7 : 6;
170 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
171 if (section == kDetails) {
173 } else if (section == kMetadata) {
174 return 1 + [object.metadata count];
175 } else if (section == cdnURLSection) {
177 } else if (section == actionsSection) {
178 return fileDownloaded ? 2 : 1;
179 } else if (section == kPublic) {
180 return objectIsPublic ? 2 : 1;
181 } else if (section == kPermissions) {
182 return 1 + [permissions count];
188 - (CGFloat)findLabelHeight:(NSString*)text font:(UIFont *)font {
189 CGSize textLabelSize;
190 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
192 textLabelSize = CGSizeMake(596.0, 9000.0f);
194 textLabelSize = CGSizeMake(280.0, 9000.0f);
196 CGSize stringSize = [text sizeWithFont:font constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeCharacterWrap];
197 return stringSize.height;
200 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
201 CGFloat result = aTableView.rowHeight;
203 if (indexPath.section == cdnURLSection) {
204 result = 22.0 + [self findLabelHeight:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] font:[UIFont systemFontOfSize:18.0]];
205 } else if (indexPath.section == kDetails && indexPath.row == 1) {
206 CGSize textLabelSize;
207 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
208 textLabelSize = CGSizeMake(537.0, 9000.0f);
210 textLabelSize = CGSizeMake(221.0, 9000.0f);
212 CGSize stringSize = [object.fullPath sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
213 return 22.0 + stringSize.height;
214 } else if (indexPath.section == kDetails && indexPath.row == 0) {
215 CGSize textLabelSize;
216 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
217 textLabelSize = CGSizeMake(537.0, 9000.0f);
219 textLabelSize = CGSizeMake(221.0, 9000.0f);
221 CGSize stringSize = [object.name sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
222 return 22.0 + stringSize.height;
223 } else if (indexPath.section == kPublic && indexPath.row == 1) {
224 result = 22.0 + [self findLabelHeight:[NSString stringWithFormat:@"%@%@", self.account.provider.authEndpointURL, self.object.fullPath] font:[UIFont systemFontOfSize:15.0]];
228 return MAX(aTableView.rowHeight, result);
231 - (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
233 static NSString *CellIdentifier = @"Cell";
235 UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
237 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
238 //cell.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.93];
239 cell.textLabel.backgroundColor = [UIColor clearColor];
240 cell.detailTextLabel.backgroundColor = [UIColor clearColor];
241 cell.detailTextLabel.numberOfLines = 0;
242 cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
245 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
246 cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
249 cell.detailTextLabel.textAlignment = UITextAlignmentRight;
250 cell.accessoryView = nil;
251 cell.textLabel.font = [UIFont boldSystemFontOfSize:17.0];
253 // Configure the cell...
254 if (indexPath.section == kDetails) {
255 cell.accessoryType = UITableViewCellAccessoryNone;
256 cell.selectionStyle = UITableViewCellSelectionStyleNone;
257 cell.accessoryView = nil;
258 if (indexPath.row == 0) {
259 cell.textLabel.text = @"Name";
260 cell.detailTextLabel.text = object.name;
261 } else if (indexPath.row == 1) {
262 cell.textLabel.text = @"Full Path";
263 cell.detailTextLabel.text = object.fullPath;
264 } else if (indexPath.row == 2) {
265 cell.textLabel.text = @"Size";
266 cell.detailTextLabel.text = [object humanizedBytes];
267 } else if (indexPath.row == 3) {
268 cell.textLabel.text = @"Type";
269 cell.detailTextLabel.text = object.contentType;
271 } else if (indexPath.section == kMetadata) {
272 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
273 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
274 cell.accessoryView = nil;
275 if (indexPath.row == [object.metadata count]) {
276 cell.textLabel.text = @"Add Metadata";
277 cell.detailTextLabel.text = @"";
279 NSString *key = [[object.metadata allKeys] objectAtIndex:indexPath.row];
280 NSString *value = [object.metadata objectForKey:key];
281 NSString *metadataKeyCellText = key;
282 NSString *metadataValueCellText = value;
283 if ([metadataKeyCellText length] > maxMetadataViewableLength) {
284 metadataKeyCellText = [metadataKeyCellText substringToIndex:(maxMetadataViewableLength - 3)];
285 metadataKeyCellText = [metadataKeyCellText stringByAppendingString:@"..."];
287 if ([metadataValueCellText length] > maxMetadataViewableLength) {
288 metadataValueCellText = [metadataValueCellText substringToIndex:(maxMetadataViewableLength - 3)];
289 metadataValueCellText = [metadataValueCellText stringByAppendingString:@"..."];
292 cell.textLabel.text = metadataKeyCellText;
293 cell.detailTextLabel.text = metadataValueCellText;
295 } else if (indexPath.section == kPublic) {
296 if (indexPath.row == 0) {
297 cell.textLabel.text = @"Public URL";
298 cell.detailTextLabel.text = @"";
299 cell.accessoryView = objectIsPublicSwitch;
300 cell.accessoryType = UITableViewCellAccessoryNone;
301 cell.selectionStyle = UITableViewCellSelectionStyleNone;
303 else if (indexPath.row == 1) {
304 cell.textLabel.numberOfLines = 0;
305 cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
306 cell.textLabel.text = [NSString stringWithFormat:@"%@%@", self.account.provider.authEndpointURL, object.publicURI];
307 cell.textLabel.font = [UIFont systemFontOfSize:15.0];
308 cell.selectionStyle = UITableViewCellSelectionStyleNone;
309 cell.accessoryView = UITableViewCellAccessoryNone;
310 cell.detailTextLabel.text = @"";
312 } else if (indexPath.section == kPermissions) {
313 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
314 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
315 cell.accessoryView = nil;
316 if (indexPath.row == [permissions count]) {
317 cell.textLabel.text = @"Add Permissions";
318 cell.detailTextLabel.text = @"";
321 NSString *user = [[permissions allKeys] objectAtIndex:indexPath.row];
322 cell.textLabel.text = user;
323 cell.detailTextLabel.text = [permissions objectForKey:user];
325 } else if (indexPath.section == cdnURLSection) {
326 cell.detailTextLabel.textAlignment = UITextAlignmentLeft;
327 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
328 cell.textLabel.text = @"";
329 cell.detailTextLabel.text = [[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
330 } else if (indexPath.section == actionsSection) {
331 cell.accessoryView = nil;
332 if (performingAction) {
333 cell.textLabel.textColor = [UIColor grayColor];
334 cell.selectionStyle = UITableViewCellSelectionStyleNone;
335 cell.accessoryType = UITableViewCellAccessoryNone;
337 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
338 cell.selectionStyle = UITableViewCellAccessoryNone;
341 if (indexPath.row == 0) {
342 if (fileDownloaded) {
343 cell.textLabel.text = @"Open File";
344 cell.accessoryType = UITableViewCellAccessoryNone;
345 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
347 if (fileDownloading) {
348 cell.accessoryView = downloadProgressView;
349 // TODO: if you leave this view while downloading, there's EXC_BAD_ACCESS
350 cell.textLabel.text = @"Downloading";
352 cell.textLabel.text = @"Download File";
355 cell.detailTextLabel.text = @"";
357 } else if (indexPath.row == 1) {
358 cell.textLabel.text = @"Email File as Attachment";
359 cell.detailTextLabel.text = @"";
360 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
362 } else if (indexPath.section == deleteSection) {
363 cell.textLabel.text = @"Delete Object";
364 cell.detailTextLabel.text = @"";
365 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
366 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
374 #pragma mark Table view delegate
376 - (void)reloadActionsTitleRow:(NSTimer *)timer {
377 [[timer.userInfo objectForKey:@"tableView"] reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
380 - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
381 if (indexPath.section == kMetadata) {
382 EditMetadataViewController *vc = [[EditMetadataViewController alloc] initWithNibName:@"EditMetadataViewController" bundle:nil];
383 NSString *metadataKey;
384 NSString *metadataValue;
386 if (indexPath.row == [self.object.metadata count]) {
389 vc.deleteEnabled = FALSE;
390 vc.navigationItem.title = @"Add Metadata";
393 metadataKey = [[self.object.metadata allKeys] objectAtIndex:indexPath.row];
394 metadataValue = [self.object.metadata objectForKey:metadataKey];
395 vc.deleteEnabled = YES;
396 vc.navigationItem.title = @"Edit Metadata";
399 vc.metadataKey = metadataKey;
400 vc.metadataValue = metadataValue;
401 vc.account = account;
402 vc.container = container;
404 [self.navigationController pushViewController:vc animated:YES];
406 } else if (indexPath.section == kPermissions) {
407 EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
410 if (indexPath.row == [permissions count]) {
412 vc.removePermissionsEnabled = NO;
413 vc.navigationItem.title = @"Add Permissions";
416 user = [[permissions allKeys] objectAtIndex:indexPath.row];
417 NSString *userPermissions = [permissions objectForKey:user];
418 if ([userPermissions rangeOfString:@"read"].location != NSNotFound)
419 vc.readPermissionSelected = YES;
421 vc.readPermissionSelected = NO;
423 if ([userPermissions rangeOfString:@"write"].location != NSNotFound)
424 vc.writePermissionSelected = YES;
426 vc.writePermissionSelected = NO;
428 vc.removePermissionsEnabled = YES;
429 vc.navigationItem.title = @"Edit Permissions";
433 vc.permissions = permissions;
434 vc.account = account;
435 vc.container = container;
437 [self.navigationController pushViewController:vc animated:YES];
439 } else if (indexPath.section == cdnURLSection) {
440 [cdnURLActionSheet showInView:self.view];
441 } else if (indexPath.section == actionsSection) {
442 if (indexPath.row == 0) {
443 if (fileDownloaded) {
444 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
445 NSString *documentsDirectory = [paths objectAtIndex:0];
446 NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
447 NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
449 self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
450 self.documentInteractionController.delegate = self;
452 if (![self.documentInteractionController presentOptionsMenuFromRect:self.view.frame inView:self.view animated:YES]) {
453 if ([self.object isPlayableMedia]) {
454 MediaViewController *vc = [[MediaViewController alloc] initWithNibName:@"MediaViewController" bundle:nil];
455 vc.container = self.container;
456 vc.object = self.object;
457 [self.navigationController pushViewController:vc animated:YES];
460 [self alert:@"Error" message:@"This file could not be opened."];
461 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
465 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];
468 if (!fileDownloading) {
470 fileDownloading = YES;
471 [self.account.manager getObject:self.container object:self.object downloadProgressDelegate:self];
472 [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
476 } else if (indexPath.row == 1) {
478 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
479 NSString *documentsDirectory = [paths objectAtIndex:0];
480 NSString *shortPath = @"";
482 if (self.container && [self.container respondsToSelector:@selector(name)] && self.object && [self.object respondsToSelector:@selector(fullPath)]) {
483 shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
486 NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
487 NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
490 if (![MFMailComposeViewController canSendMail]) {
491 [self alert:@"Cannot send mail" message:@"Your device has not been configured for sending email"];
492 [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
495 MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
497 vc.mailComposeDelegate = self;
498 [vc setSubject:self.object.name];
499 [vc addAttachmentData:data mimeType:self.object.contentType fileName:self.object.name];
500 [vc setMessageBody:@"" isHTML:NO];
501 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
502 vc.modalPresentationStyle = UIModalPresentationPageSheet;
504 [self presentModalViewController:vc animated:YES];
508 } else if (indexPath.section == deleteSection) {
509 [deleteActionSheet showInView:self.view];
513 - (void)setProgress:(float)newProgress {
514 [downloadProgressView setProgress:newProgress animated:YES];
515 if (newProgress >= 1.0) {
516 fileDownloading = NO;
517 fileDownloaded = YES;
519 [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:1 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationBottom];
520 [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
525 #pragma mark Document Interation Controller Delegate
527 - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *) controller {
528 return self.navigationController;
531 - (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controllers {
532 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];
536 #pragma mark Action Sheet Delegate
538 - (void)deleteObjectRow {
539 [self.folderViewController.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
542 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
543 if ([actionSheet isEqual:deleteActionSheet]) {
544 if (buttonIndex == 0) {
545 // delete the file and pop out
547 NSString *activityMessage = @"Deleting file";
549 activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
550 [activityIndicatorView addToView:self.view];
552 [self.account.manager deleteObject:self.container object:self.object];
554 deleteSuccessObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"deleteObjectSucceeded" object:self.object
555 queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification)
557 [activityIndicatorView removeFromSuperviewAndRelease];
558 performingAction = NO;
559 [self.folder.objects removeObjectForKey:self.object.name];
560 [self.navigationController popViewControllerAnimated:YES];
561 if ([self.folder.objects count] + [self.folder.folders count] == 0) {
562 [self.folderViewController.tableView reloadData];
564 [self.folderViewController.tableView selectRowAtIndexPath:selectedIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
565 [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(deleteObjectRow) userInfo:nil repeats:NO];
567 [[NSNotificationCenter defaultCenter] removeObserver:deleteSuccessObserver];
570 deleteFailureObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"deleteObjectFailed" object:self.object
571 queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification)
573 [activityIndicatorView removeFromSuperviewAndRelease];
574 [self hideToolbarActivityMessage];
575 performingAction = NO;
576 [self alert:@"There was a problem deleting this file." request:[notification.userInfo objectForKey:@"request"]];
578 [[NSNotificationCenter defaultCenter] removeObserver:deleteFailureObserver];
583 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:deleteSection];
584 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
585 } else if ([actionSheet isEqual:cdnURLActionSheet]) {
586 NSURL *url = [NSURL URLWithString:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
588 if (buttonIndex == 0) {
589 // copy to pasteboard
590 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
591 [pasteboard setString:[url description]];
592 } else if (buttonIndex == 1) {
594 UIApplication *application = [UIApplication sharedApplication];
595 if ([application canOpenURL:url]) {
596 [application openURL:url];
598 [self alert:@"Error" message:[NSString stringWithFormat:@"This URL cannot be opened.\n%@", url]];
600 } else if (buttonIndex == 2) {
601 // email link to file
602 MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
603 vc.mailComposeDelegate = self;
604 [vc setSubject:self.object.name];
605 [vc setMessageBody:[url description] isHTML:NO];
606 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
607 vc.modalPresentationStyle = UIModalPresentationPageSheet;
609 [self presentModalViewController:vc animated:YES];
611 } else if (buttonIndex == 3) {
612 // tweet link to file
613 UIApplication *app = [UIApplication sharedApplication];
614 NSURL *twitterURL = [NSURL URLWithString:[NSString stringWithFormat:@"twitter://post?message=%@", [[url description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
615 if ([app canOpenURL:twitterURL]) {
616 [app openURL:twitterURL];
619 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:cdnURLSection] animated:YES];
624 #pragma mark Mail Composer Delegate
626 // Dismisses the email composition interface when users tap Cancel or Send.
627 - (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
628 [self dismissModalViewControllerAnimated:YES];
629 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:actionsSection] animated:YES];
633 #pragma mark Switches
635 - (void)objectIsPublicSwitchChanged:(id)sender
637 NSString *activityMessage = [NSString stringWithFormat:@"Enabling public link.."];
638 self.oldPubicURI = object.publicURI;
640 if (objectIsPublic) {
641 activityMessage = [NSString stringWithFormat:@"Disabling public link.."];
642 object.publicURI = @"";
645 object.publicURI = @"TRUE";
648 activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
649 [activityIndicatorView addToView:self.view scrollOffset:self.tableView.contentOffset.y];
651 objectIsPublic = !objectIsPublic;
652 [self.account.manager writeObjectMetadata:container object:object];
653 if (objectIsPublic) {
654 getObjectInfoSuccessObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"getObjectInfoSucceeded"
656 queue:[NSOperationQueue mainQueue]
657 usingBlock:^(NSNotification *notification)
659 [activityIndicatorView removeFromSuperviewAndRelease];
660 OpenStackRequest *request = [notification.userInfo objectForKey:@"request"];
661 object.publicURI = [request.responseHeaders objectForKey:@"X-Object-Public"];
662 NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:kPublic];
663 [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
664 withRowAnimation:UITableViewRowAnimationBottom];
666 [[NSNotificationCenter defaultCenter] removeObserver:getObjectInfoSuccessObserver];
669 getObjectInfoFailureObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"getObjectInfoFailed"
671 queue:[NSOperationQueue mainQueue]
672 usingBlock:^(NSNotification *notification)
674 [activityIndicatorView removeFromSuperviewAndRelease];
675 [self alert:@"There was a problem retrieving the public link from the server." request:[notification.userInfo objectForKey:@"request"]];
676 [[NSNotificationCenter defaultCenter] removeObserver:getObjectInfoFailureObserver];
679 enablePublicSuccessObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"writeObjectMetadataSucceeded"
681 queue:[NSOperationQueue mainQueue]
682 usingBlock:^(NSNotification* notification)
684 NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:kPublic];
685 if (objectIsPublic) {
686 [self.account.manager getObjectInfo:container object:object];
689 [activityIndicatorView removeFromSuperviewAndRelease];
690 [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
691 withRowAnimation:UITableViewRowAnimationTop];
694 [[NSNotificationCenter defaultCenter] removeObserver:enablePublicSuccessObserver];
697 enablePublicFailureObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"writeObjectMetadataFailed"
699 queue:[NSOperationQueue mainQueue]
700 usingBlock:^(NSNotification* notification)
702 [activityIndicatorView removeFromSuperviewAndRelease];
703 objectIsPublic = !objectIsPublic;
704 objectIsPublicSwitch.on = !objectIsPublicSwitch.on;
705 object.publicURI = oldPubicURI;
706 [self alert:@"There was a problem enabling the public link." request:[notification.userInfo objectForKey:@"request"]];
707 [[NSNotificationCenter defaultCenter] removeObserver:enablePublicFailureObserver];
716 #pragma mark Memory management
720 [downloadProgressView release];
721 [deleteActionSheet release];
722 [cdnURLActionSheet release];
724 [folderViewController release];
725 [objectIsPublicSwitch release];
726 [permissions release];
727 [documentInteractionController release];