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"
33 #define maxMetadataViewableLength 12
36 // TODO: use etag to reset download
37 // TODO: try downloading directly to the file to save memory. don't use object.data
43 Content Type text/plain
46 Key Value -> tap goes to a metadata item VC to edit or delete
48 Add Metadata... (if max not already reached)
50 CDN URL sections (action sheet to copy, open in safari, and email link)
52 Download File (if downloaded, Open File and Mail File as Attachment)
53 "After you download the file, you'll be able to attempt to open it and mail is as an attachment."
58 @implementation StorageObjectViewController
60 @synthesize account, container, folder, object, tableView, folderViewController;
61 @synthesize oldPubicURI, documentInteractionController, actionSelectedIndexPath, objectIsReadOnly, versionID;
63 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
64 return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
67 - (void)setBackgroundView {
69 UIImageView *logo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cloudfiles-large.png"]];
71 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
72 UIView *backgroundContainer = [[UIView alloc] init];
73 backgroundContainer.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
74 backgroundContainer.backgroundColor = [UIColor iPadTableBackgroundColor];
75 logo.contentMode = UIViewContentModeScaleAspectFit;
76 logo.frame = CGRectMake(100.0, 100.0, 1000.0, 1000.0);
78 [backgroundContainer addSubview:logo];
79 tableView.backgroundView = backgroundContainer;
80 [backgroundContainer release];
82 self.tableView.backgroundView = nil;
87 - (IBAction)homeButtonPressed:(id)sender {
88 [self.navigationController popToRootViewControllerAnimated:YES];
92 #pragma mark View lifecycle
97 objectIsPublicSwitch = [[UISwitch alloc] init];
98 [objectIsPublicSwitch addTarget:self action:@selector(objectIsPublicSwitchChanged:) forControlEvents:UIControlEventValueChanged];
100 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];
101 permissions = [[NSMutableDictionary alloc] init];
102 if (object.sharing.length > 0) {
103 NSArray *sharingArray = [object.sharing componentsSeparatedByString:@";"];
104 for (NSString *typeSpecificPermissions in sharingArray) {
105 NSArray *array=[typeSpecificPermissions componentsSeparatedByString:@"="];
106 NSString *permissionsType = [array objectAtIndex:0];
107 if ([permissionsType hasPrefix:@" "])
108 permissionsType = [permissionsType substringFromIndex:1];
110 NSArray *users = [[array objectAtIndex:1] componentsSeparatedByString:@","];
111 for (NSString *user in users) {
112 [permissions setObject:permissionsType forKey:user];
116 objectIsReadOnly = NO;
117 if (account.sharingAccount) {
118 if ([permissions count] > 0) {
119 objectIsReadOnly = [[permissions objectForKey:[account username]] isEqualToString:@"read"];
121 objectIsPublicSwitch.enabled = NO;
124 downloadProgressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
125 fileDownloading = ([self.account.manager.objectDownloadRequests objectForKey:object.fullPath] != nil) ? YES : NO;
126 if (fileDownloading) {
127 OpenStackRequest *request = [self.account.manager.objectDownloadRequests objectForKey:object.fullPath];
128 self.actionSelectedIndexPath = [request.userInfo objectForKey:@"actionSelectedIndexPath"];
129 [request setDownloadProgressDelegate:self];
131 appDelegate = [[UIApplication sharedApplication] delegate];
134 - (void)viewWillAppear:(BOOL)animated {
135 [super viewWillAppear:animated];
136 self.navigationItem.title = object.name;
138 NSIndexPath *selection = [self.tableView indexPathForSelectedRow];
140 [self.tableView deselectRowAtIndexPath:selection animated:YES];
141 if ([appDelegate.cachedObjectsDictionary objectForKey:object.hash] != nil) {
142 if ([[NSFileManager defaultManager] fileExistsAtPath:[appDelegate.cachedObjectsDictionary objectForKey:object.hash]])
143 fileDownloaded = YES;
146 [appDelegate.cachedObjectsDictionary removeObjectForKey:object.hash];
147 [appDelegate saveCacheDictionary];
153 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
154 CGRect rect = downloadProgressView.frame;
155 rect.size.width = 440.0;
156 downloadProgressView.frame = rect;
159 //[self setBackgroundView];
161 publicLinkSection = 3;
162 permissionsSection = 4;
163 if (self.container.cdnEnabled) {
174 publicLinkSection = -1;
175 permissionsSection = -1;
176 versionsSection = -1;
178 objectIsReadOnly = YES;
180 if (account.sharingAccount || account.shared) {
181 versionsSection = -1;
186 // let's see if we can tweet
187 UIApplication *app = [UIApplication sharedApplication];
188 NSURL *twitterURL = [NSURL URLWithString:@"twitter://post?message=test"];
190 if ([app canOpenURL:twitterURL]) {
191 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];
193 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];
195 objectIsPublic = ([object.publicURI length]);
196 objectIsPublicSwitch.on = objectIsPublic;
197 [self.tableView reloadData];
200 - (void)viewDidAppear:(BOOL)animated {
201 if (object.metadata == nil) {
202 [self reloadMetadataSection];
207 #pragma mark Table view data source
209 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
211 int numberOfSections = 8;
212 if (!self.container.cdnEnabled)
214 if (deleteSection < 0)
216 if (versionsSection < 0)
218 if (publicLinkSection < 0)
220 if (permissionsSection < 0)
223 return numberOfSections;
226 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
227 if (section == kDetails) {
229 } else if (section == kMetadata) {
230 if (objectIsReadOnly) {
231 return [object.metadata count];
233 return 1 + [object.metadata count];
235 } else if (section == cdnURLSection) {
237 } else if (section == actionsSection) {
239 } else if (section == publicLinkSection) {
240 return objectIsPublic ? 2 : 1;
241 } else if (section == permissionsSection) {
242 if (account.sharingAccount || objectIsReadOnly)
243 return [permissions count];
245 return 1 + [permissions count];
251 - (CGFloat)findLabelHeight:(NSString*)text font:(UIFont *)font {
252 CGSize textLabelSize;
253 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
255 textLabelSize = CGSizeMake(596.0, 9000.0f);
257 textLabelSize = CGSizeMake(280.0, 9000.0f);
259 CGSize stringSize = [text sizeWithFont:font constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeCharacterWrap];
260 return stringSize.height;
263 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
264 CGFloat result = aTableView.rowHeight;
266 if (indexPath.section == cdnURLSection) {
267 result = 22.0 + [self findLabelHeight:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] font:[UIFont systemFontOfSize:18.0]];
268 } else if (indexPath.section == kDetails && indexPath.row == 1) {
269 CGSize textLabelSize;
270 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
271 textLabelSize = CGSizeMake(537.0, 9000.0f);
273 textLabelSize = CGSizeMake(221.0, 9000.0f);
275 CGSize stringSize = [object.fullPath sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
276 return 22.0 + stringSize.height;
277 } else if (indexPath.section == kDetails && indexPath.row == 0) {
278 CGSize textLabelSize;
279 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
280 textLabelSize = CGSizeMake(537.0, 9000.0f);
282 textLabelSize = CGSizeMake(221.0, 9000.0f);
284 CGSize stringSize = [object.name sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
285 return 22.0 + stringSize.height;
286 } else if (indexPath.section == publicLinkSection && indexPath.row == 1) {
287 NSString *publicLinkUrl = [NSString stringWithFormat:@"%@%@",
288 account.pithosPublicLinkURLPrefix,
289 self.object.publicURI];
291 result = 30.0 + [self findLabelHeight:publicLinkUrl font:[UIFont systemFontOfSize:15.0]];
293 return MAX(aTableView.rowHeight, result);
296 - (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
298 static NSString *CellIdentifier = @"Cell";
299 static NSString *TextViewCellIdentifier = @"TextViewCell";
300 if (indexPath.section == publicLinkSection) {
301 if (indexPath.row == 1) {
302 TextViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:TextViewCellIdentifier];
304 cell = [[[TextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TextViewCellIdentifier] autorelease];
305 cell.textView.frame = cell.contentView.frame;
308 cell.selectionStyle = UITableViewCellSelectionStyleNone;
309 cell.textView.backgroundColor = [UIColor clearColor];
310 cell.textView.font = [UIFont systemFontOfSize:15.0];
311 cell.textView.dataDetectorTypes = UIDataDetectorTypeLink;
312 cell.textView.text = [NSString stringWithFormat:@"%@%@",
313 account.pithosPublicLinkURLPrefix,
314 [NSString encodeToPercentEscape:self.object.publicURI charactersToEncode:@"!*'();:@&=+$,?%#[]"]];
316 cell.selectionStyle = UITableViewCellSelectionStyleNone;
317 cell.accessoryView = UITableViewCellAccessoryNone;
322 UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
324 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
325 cell.textLabel.backgroundColor = [UIColor clearColor];
326 cell.detailTextLabel.backgroundColor = [UIColor clearColor];
327 cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
329 cell.textLabel.textColor = [UIColor blackColor];
330 cell.detailTextLabel.numberOfLines = 0;
332 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
333 cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
335 cell.userInteractionEnabled = YES;
336 cell.detailTextLabel.textAlignment = UITextAlignmentRight;
337 cell.accessoryView = nil;
338 cell.textLabel.font = [UIFont boldSystemFontOfSize:17.0];
340 // Configure the cell...
341 if (indexPath.section == kDetails) {
342 cell.accessoryType = UITableViewCellAccessoryNone;
343 cell.selectionStyle = UITableViewCellSelectionStyleNone;
344 cell.accessoryView = nil;
345 if (indexPath.row == 0) {
346 cell.textLabel.text = @"Name";
347 cell.detailTextLabel.text = object.name;
348 } else if (indexPath.row == 1) {
349 cell.textLabel.text = @"Full Path";
350 cell.detailTextLabel.text = object.fullPath;
351 } else if (indexPath.row == 2) {
352 cell.textLabel.text = @"Size";
353 cell.detailTextLabel.text = [object humanizedBytes];
354 } else if (indexPath.row == 3) {
355 cell.textLabel.text = @"Type";
356 cell.detailTextLabel.text = object.contentType;
358 } else if (indexPath.section == kMetadata) {
359 if (objectIsReadOnly) {
360 cell.accessoryType = UITableViewCellAccessoryNone;
361 cell.selectionStyle = UITableViewCellSelectionStyleNone;
362 cell.userInteractionEnabled = NO;
365 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
366 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
369 cell.accessoryView = nil;
370 if (indexPath.row == [object.metadata count]) {
371 cell.textLabel.text = @"Add Metadata";
372 cell.detailTextLabel.text = @"";
374 NSString *key = [[object.metadata allKeys] objectAtIndex:indexPath.row];
375 NSString *value = [object.metadata objectForKey:key];
376 NSString *metadataKeyCellText = key;
377 NSString *metadataValueCellText = value;
378 if ([metadataKeyCellText length] > maxMetadataViewableLength) {
379 metadataKeyCellText = [metadataKeyCellText substringToIndex:(maxMetadataViewableLength - 3)];
380 metadataKeyCellText = [metadataKeyCellText stringByAppendingString:@"..."];
382 if ([metadataValueCellText length] > maxMetadataViewableLength) {
383 metadataValueCellText = [metadataValueCellText substringToIndex:(maxMetadataViewableLength - 3)];
384 metadataValueCellText = [metadataValueCellText stringByAppendingString:@"..."];
387 cell.textLabel.text = metadataKeyCellText;
388 cell.detailTextLabel.text = metadataValueCellText;
390 } else if (indexPath.section == publicLinkSection) {
391 if (indexPath.row == 0) {
392 cell.textLabel.text = @"Public URL";
393 cell.detailTextLabel.text = @"";
394 cell.accessoryView = objectIsPublicSwitch;
395 cell.accessoryType = UITableViewCellAccessoryNone;
396 cell.selectionStyle = UITableViewCellSelectionStyleNone;
398 } else if (indexPath.section == permissionsSection) {
399 if (account.sharingAccount) {
400 cell.accessoryType = UITableViewCellAccessoryNone;
401 cell.selectionStyle = UITableViewCellSelectionStyleNone;
402 cell.userInteractionEnabled = NO;
405 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
406 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
408 cell.accessoryView = nil;
410 if (indexPath.row == [permissions count]) {
411 cell.textLabel.text = @"Share";
412 cell.detailTextLabel.text = @"";
414 NSString *user = [[permissions allKeys] objectAtIndex:indexPath.row];
415 cell.textLabel.text = user;
416 NSString *accessType;
417 if ([[permissions objectForKey:user] isEqualToString:@"write"])
418 accessType = @"Read/Write";
420 accessType = @"Read Only";
421 cell.detailTextLabel.numberOfLines = 1;
422 cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
423 cell.detailTextLabel.text = accessType;
425 } else if (indexPath.section == cdnURLSection) {
426 cell.detailTextLabel.textAlignment = UITextAlignmentLeft;
427 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
428 cell.textLabel.text = @"";
429 cell.detailTextLabel.text = [[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
430 } else if (indexPath.section == actionsSection) {
431 if (fileDownloading) {
432 if (actionSelectedIndexPath.row == indexPath.row) {
433 cell.accessoryView = downloadProgressView;
435 cell.textLabel.textColor = [UIColor grayColor];
436 cell.selectionStyle = UITableViewCellSelectionStyleNone;
439 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
440 cell.accessoryView = nil;
442 if (indexPath.row == 0) {
443 cell.textLabel.text = @"Open File";
444 cell.accessoryType = UITableViewCellAccessoryNone;
445 cell.detailTextLabel.text = @"";
447 } else if (indexPath.row == 1) {
448 cell.textLabel.text = @"Email File as Attachment";
449 cell.detailTextLabel.text = @"";
450 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
452 } else if (indexPath.section == deleteSection) {
453 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
454 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
455 cell.textLabel.text = @"Delete Object";
456 cell.detailTextLabel.text = @"";
458 } else if (indexPath.section == versionsSection) {
459 cell.selectionStyle = UITableViewCellSelectionStyleBlue;
460 cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
461 cell.textLabel.text = @"Versions";
462 cell.detailTextLabel.text = @"";
471 #pragma mark Table view delegate
473 - (void)reloadActionsTitleRow:(NSTimer *)timer {
474 [[timer.userInfo objectForKey:@"tableView"] reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
477 - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
478 if (indexPath.section == kMetadata) {
479 EditMetadataViewController *vc = [[EditMetadataViewController alloc] initWithNibName:@"EditMetadataViewController" bundle:nil];
480 NSString *metadataKey;
481 NSString *metadataValue;
483 if (indexPath.row == [self.object.metadata count]) {
486 vc.removeMetadataEnabled = FALSE;
487 vc.navigationItem.title = @"Add Metadata";
490 metadataKey = [[self.object.metadata allKeys] objectAtIndex:indexPath.row];
491 metadataValue = [self.object.metadata objectForKey:metadataKey];
492 vc.removeMetadataEnabled = YES;
493 vc.navigationItem.title = @"Edit Metadata";
496 vc.metadataKey = metadataKey;
497 vc.metadataValue = metadataValue;
498 vc.account = account;
499 vc.container = container;
501 [self.navigationController pushViewController:vc animated:YES];
503 } else if (indexPath.section == permissionsSection) {
504 EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
507 if (indexPath.row == [permissions count]) {
509 vc.removePermissionsEnabled = NO;
510 vc.navigationItem.title = @"Share";
513 user = [[permissions allKeys] objectAtIndex:indexPath.row];
514 NSString *userPermissions = [permissions objectForKey:user];
515 if ([userPermissions rangeOfString:@"read"].location != NSNotFound)
516 vc.readPermissionSelected = YES;
518 vc.readPermissionSelected = NO;
520 if ([userPermissions rangeOfString:@"write"].location != NSNotFound)
521 vc.writePermissionSelected = YES;
523 vc.writePermissionSelected = NO;
525 vc.removePermissionsEnabled = YES;
526 vc.navigationItem.title = @"Edit Sharing";
530 vc.permissions = permissions;
531 vc.account = account;
532 vc.container = container;
534 vc.folderViewController = folderViewController;
535 [self.navigationController pushViewController:vc animated:YES];
537 } else if (indexPath.section == cdnURLSection) {
538 [cdnURLActionSheet showInView:self.view];
539 } else if (indexPath.section == actionsSection) {
540 if (!fileDownloaded) {
541 if (!fileDownloading) {
543 fileDownloading = YES;
544 self.actionSelectedIndexPath = indexPath;
545 [self.tableView reloadData];
546 NSMutableDictionary *requestUserInfo = [NSDictionary dictionaryWithObject:indexPath forKey:@"actionSelectedIndexPath"];
547 [[self.account.manager getObject:self.container object:self.object downloadProgressDelegate:self requestUserInfo:requestUserInfo version:versionID]
548 success:^(OpenStackRequest *request) {
549 fileDownloaded = YES;
550 fileDownloading = NO;
551 if ([self.navigationController.visibleViewController isEqual:self])
552 [self.tableView.delegate tableView:self.tableView didSelectRowAtIndexPath:indexPath];
553 [self.tableView reloadData];
554 [self.folderViewController.tableView reloadData];
556 failure:^(OpenStackRequest *request) {
558 fileDownloading = NO;
559 [self alert:@"File failed to download" request:request];
560 [self.tableView reloadData];
562 [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexPath.row inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
564 } else if (indexPath.row == 0) {
565 if (fileDownloaded) {
566 NSString *filePath = [appDelegate.cachedObjectsDictionary objectForKey:object.hash];
567 self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
568 self.documentInteractionController.delegate = self;
569 self.documentInteractionController.name = object.name;
571 UITableViewCell *openFileCell = [self.tableView cellForRowAtIndexPath:indexPath];
572 CGRect frameToPresentMenuFrom = CGRectMake(openFileCell.frame.origin.x,
573 openFileCell.frame.origin.y - self.tableView.contentOffset.y,
574 openFileCell.frame.size.width,
575 openFileCell.frame.size.height);
576 if (![self.documentInteractionController presentOptionsMenuFromRect:frameToPresentMenuFrom inView:self.view animated:YES]) {
577 if ([self.object isPlayableMedia]) {
578 MediaViewController *vc = [[MediaViewController alloc] initWithNibName:@"MediaViewController" bundle:nil];
579 vc.container = self.container;
580 vc.object = self.object;
581 [self.navigationController pushViewController:vc animated:YES];
584 [self alert:@"Error" message:@"This file could not be opened."];
585 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
589 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];
592 } else if (indexPath.row == 1) {
594 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
595 NSString *documentsDirectory = [paths objectAtIndex:0];
596 NSString *shortPath = @"";
598 if (self.container && [self.container respondsToSelector:@selector(name)] && self.object && [self.object respondsToSelector:@selector(fullPath)]) {
599 shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
602 NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
603 NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
606 if (![MFMailComposeViewController canSendMail]) {
607 [self alert:@"Cannot send mail" message:@"Your device has not been configured for sending email"];
608 [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
611 MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
613 vc.mailComposeDelegate = self;
614 [vc setSubject:self.object.name];
615 [vc addAttachmentData:data mimeType:self.object.contentType fileName:self.object.name];
616 [vc setMessageBody:@"" isHTML:NO];
617 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
618 vc.modalPresentationStyle = UIModalPresentationPageSheet;
620 [self presentModalViewController:vc animated:YES];
624 } else if (indexPath.section == deleteSection) {
625 [deleteActionSheet showInView:self.view];
626 } else if (indexPath.section == versionsSection) {
627 ObjectVersionsViewController *vc = [[ObjectVersionsViewController alloc] initWithNibName:@"ObjectVersionsViewController" bundle:nil];
628 vc.account = account;
629 vc.container = container;
631 [self.navigationController pushViewController:vc animated:YES];
636 - (void)setProgress:(float)newProgress {
637 [downloadProgressView setProgress:newProgress animated:YES];
638 if (newProgress >= 1.0) {
639 fileDownloading = NO;
640 fileDownloaded = YES;
641 [self.tableView reloadData];
647 #pragma mark Document Interation Controller Delegate
649 - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *) controller {
650 return self.navigationController;
653 - (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controllers {
654 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];
658 #pragma mark Action Sheet Delegate
660 - (void)deleteObjectRow {
661 [self.folder.objects removeObjectForKey:self.object.name];
662 [self.folderViewController.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
663 self.folderViewController.refreshButton.enabled = YES;
666 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
667 if ([actionSheet isEqual:deleteActionSheet]) {
668 if (buttonIndex == 0) {
669 // delete the file and pop out
671 NSString *activityMessage = @"Deleting file";
672 [activityIndicatorView release];
673 activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
674 [activityIndicatorView addToView:self.view];
676 self.folderViewController.refreshButton.enabled = NO;
677 [[self.account.manager deleteObject:self.container object:self.object]
678 success:^(OpenStackRequest *request) {
679 [activityIndicatorView removeFromSuperview];
680 performingAction = NO;
681 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
682 [self.navigationController popViewControllerAnimated:YES];
684 self.folderViewController.needsRefreshing = YES;
685 } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
686 self.folderViewController.selectedObjectViewController = nil;
688 [self.folderViewController setDetailViewController];
690 [self.folderViewController refreshButtonPressed:nil];
692 if (self.folder.objects.count == 1)
693 [self.folder.objects removeObjectForKey:self.object.name];
695 if ([self.folder.objects count] + [self.folder.folders count] == 0) {
696 [self.folderViewController.tableView reloadData];
698 [self.folderViewController.tableView selectRowAtIndexPath:selectedIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
699 [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(deleteObjectRow) userInfo:nil repeats:NO];
702 failure:^(OpenStackRequest *request) {
703 [activityIndicatorView removeFromSuperview];
704 [self hideToolbarActivityMessage];
705 performingAction = NO;
706 [self alert:@"There was a problem deleting this file." request:request];
709 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:deleteSection];
710 [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
711 } else if ([actionSheet isEqual:cdnURLActionSheet]) {
712 NSURL *url = [NSURL URLWithString:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
714 if (buttonIndex == 0) {
715 // copy to pasteboard
716 UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
717 [pasteboard setString:[url description]];
718 } else if (buttonIndex == 1) {
720 UIApplication *application = [UIApplication sharedApplication];
721 if ([application canOpenURL:url]) {
722 [application openURL:url];
724 [self alert:@"Error" message:[NSString stringWithFormat:@"This URL cannot be opened.\n%@", url]];
726 } else if (buttonIndex == 2) {
727 // email link to file
728 MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
729 vc.mailComposeDelegate = self;
730 [vc setSubject:self.object.name];
731 [vc setMessageBody:[url description] isHTML:NO];
732 if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
733 vc.modalPresentationStyle = UIModalPresentationPageSheet;
735 [self presentModalViewController:vc animated:YES];
737 } else if (buttonIndex == 3) {
738 // tweet link to file
739 UIApplication *app = [UIApplication sharedApplication];
740 NSURL *twitterURL = [NSURL URLWithString:[NSString stringWithFormat:@"twitter://post?message=%@", [[url description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
741 if ([app canOpenURL:twitterURL]) {
742 [app openURL:twitterURL];
745 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:cdnURLSection] animated:YES];
750 #pragma mark Mail Composer Delegate
752 // Dismisses the email composition interface when users tap Cancel or Send.
753 - (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {
754 [self dismissModalViewControllerAnimated:YES];
755 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:actionsSection] animated:YES];
759 #pragma mark Switches
761 - (void)objectIsPublicSwitchChanged:(id)sender
763 NSString *activityMessage = [NSString stringWithFormat:@"Enabling public link.."];
764 self.oldPubicURI = object.publicURI;
766 if (objectIsPublic) {
767 activityMessage = [NSString stringWithFormat:@"Disabling public link.."];
768 object.publicURI = @"";
771 object.publicURI = @"TRUE";
773 [activityIndicatorView release];
774 activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
775 [activityIndicatorView addToView:self.view scrollOffset:self.tableView.contentOffset.y];
777 objectIsPublic = !objectIsPublic;
778 [[self.account.manager writeObjectMetadata:container object:object]
779 success:^(OpenStackRequest *request) {
780 NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:publicLinkSection];
781 if (objectIsPublic) {
782 [[self.account.manager getObjectInfo:container object:object version:versionID]
783 success:^(OpenStackRequest *request) {
784 [activityIndicatorView removeFromSuperview];
785 object.publicURI = [request.responseHeaders objectForKey:@"X-Object-Public"];
786 NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:publicLinkSection];
787 [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
788 withRowAnimation:UITableViewRowAnimationBottom];
790 failure:^(OpenStackRequest *request) {
791 [activityIndicatorView removeFromSuperview];
792 [self alert:@"There was a problem retrieving the public link from the server." request:request];
796 [activityIndicatorView removeFromSuperview];
797 [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
798 withRowAnimation:UITableViewRowAnimationTop];
801 failure:^(OpenStackRequest *request) {
802 [activityIndicatorView removeFromSuperview];
803 objectIsPublic = !objectIsPublic;
804 objectIsPublicSwitch.on = !objectIsPublicSwitch.on;
805 object.publicURI = oldPubicURI;
806 [self alert:@"There was a problem enabling the public link." request:request];
814 #pragma mark Memory management
817 if (fileDownloading) {
818 OpenStackRequest *request = [self.account.manager.objectDownloadRequests objectForKey:object.fullPath];
819 [request setDownloadProgressDelegate:nil];
822 [downloadProgressView release];
823 [deleteActionSheet release];
824 [cdnURLActionSheet release];
826 [folderViewController release];
827 [objectIsPublicSwitch release];
828 [permissions release];
829 [documentInteractionController release];
830 [actionSelectedIndexPath release];
832 [activityIndicatorView release];
837 #pragma mark Helper functions
839 - (void)reloadMetadataSection {
840 NSString *activityMessage = @"Loading metadata...";
841 [activityIndicatorView release];
842 activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
843 [activityIndicatorView addToView:self.view];
844 [[self.account.manager getObjectInfo:container object:object version:versionID]
845 success:^(OpenStackRequest *request) {
846 [activityIndicatorView removeFromSuperview];
847 object.metadata = [NSMutableDictionary dictionary];
848 for (NSString *header in request.responseHeaders) {
849 NSString *metadataKey;
850 NSString *metadataValue;
851 if ([header rangeOfString:@"X-Object-Meta-"].location != NSNotFound) {
852 metadataKey = [NSString decodeFromPercentEscape:[header substringFromIndex:14]];
853 metadataValue = [NSString decodeFromPercentEscape:[request.responseHeaders objectForKey:header]];
854 [object.metadata setObject:metadataValue forKey:metadataKey];
857 NSIndexSet *metadataSections = [NSIndexSet indexSetWithIndex:kMetadata];
858 [self.tableView reloadSections:metadataSections withRowAnimation:UITableViewRowAnimationFade];
860 failure:^(OpenStackRequest *request) {
861 [activityIndicatorView removeFromSuperview];
862 [self alert:@"There was a problem retrieving the object's metadata." request:request];