Copy of rackspace-ios version 2.1.1
[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 "Container.h"
12 #import "Folder.h"
13 #import "StorageObject.h"
14 #import "AccountManager.h"
15 #import "AnimatedProgressView.h"
16 #import "UIViewController+Conveniences.h"
17 #import "MediaViewController.h"
18 #import "FolderViewController.h"
19 #import "UIColor+MoreColors.h"
20 #import "OpenStackAppDelegate.h"
21
22 #define kDetails 0
23 #define kMetadata -1
24
25 // TODO: use etag to reset download
26 // TODO: try downloading directly to the file to save memory.  don't use object.data
27
28 /*
29  Name                           whatever.txt
30  Full Path
31  Size                           123 KB
32  Content Type                   text/plain
33  
34  Metadata
35  Key                            Value -> tap goes to a metadata item VC to edit or delete
36  Key                            Value
37  Add Metadata... (if max not already reached)
38  
39  CDN URL sections (action sheet to copy, open in safari, and email link)
40  
41  Download File (if downloaded, Open File and Mail File as Attachment)
42  "After you download the file, you'll be able to attempt to open it and mail is as an attachment."
43  
44  Delete Object
45  */
46
47 @implementation StorageObjectViewController
48
49 @synthesize account, container, folder, object, tableView, folderViewController;
50
51 - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
52     return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
53 }
54
55 - (void)setBackgroundView {
56     
57     UIImageView *logo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cloudfiles-large.png"]];
58     
59     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
60         UIView *backgroundContainer = [[UIView alloc] init];
61         backgroundContainer.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
62         backgroundContainer.backgroundColor = [UIColor iPadTableBackgroundColor];
63         logo.contentMode = UIViewContentModeScaleAspectFit;
64         logo.frame = CGRectMake(100.0, 100.0, 1000.0, 1000.0);
65         logo.alpha = 0.5;        
66         [backgroundContainer addSubview:logo];
67         tableView.backgroundView = backgroundContainer;
68         [backgroundContainer release];
69     } else {        
70         self.tableView.backgroundView = nil;
71     }
72     [logo release];    
73 }
74
75
76 #pragma mark -
77 #pragma mark View lifecycle
78
79 - (void)viewDidLoad {
80     [super viewDidLoad];
81     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];
82 }
83
84 - (void)viewWillAppear:(BOOL)animated {
85     [super viewWillAppear:animated];
86     self.navigationItem.title = object.name;
87     
88     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
89     NSString *documentsDirectory = [paths objectAtIndex:0];        
90     NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
91     NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
92     
93     NSFileManager *fileManager = [NSFileManager defaultManager];
94     fileDownloaded = [fileManager fileExistsAtPath:filePath];
95     
96     downloadProgressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
97     
98     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
99         CGRect rect = downloadProgressView.frame;
100         rect.size.width = 440.0;
101         downloadProgressView.frame = rect;
102     }
103     
104     [self setBackgroundView];
105     if (self.container.cdnEnabled) {
106         cdnURLSection = 1;
107         actionsSection = 2;
108         deleteSection = 3;
109     } else {
110         cdnURLSection = -1;
111         actionsSection = 1;
112         deleteSection = 2;
113     }
114     
115     // let's see if we can tweet
116     UIApplication *app = [UIApplication sharedApplication];
117     NSURL *twitterURL = [NSURL URLWithString:@"twitter://post?message=test"];
118
119     if ([app canOpenURL:twitterURL]) {
120         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];
121     } else {
122         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];
123     }
124     
125 }
126
127 #pragma mark -
128 #pragma mark Table view data source
129
130 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
131     return self.container.cdnEnabled ? 4 : 3;
132 }
133
134 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
135     if (section == kDetails) {
136         return 4;
137     } else if (section == kMetadata) {
138         return 1 + [object.metadata count];
139     } else if (section == cdnURLSection) {
140         return 1;
141     } else if (section == actionsSection) {
142         return fileDownloaded ? 2 : 1;
143     } else if (section == deleteSection) {
144         return 1;
145     } else {
146         return 1;
147     }
148 }
149
150 /*
151 - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
152     if (section == actionsSection) {
153         return @"After you download the file, you'll be able to attempt to open it and mail it as an attachment.";
154     } else {
155         return @"";
156     }
157 }
158 */
159
160 - (CGFloat)findLabelHeight:(NSString*)text font:(UIFont *)font {
161     CGSize textLabelSize;    
162     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
163         // 616, 678
164         textLabelSize = CGSizeMake(596.0, 9000.0f);
165     } else {
166         textLabelSize = CGSizeMake(280.0, 9000.0f);
167     }
168     CGSize stringSize = [text sizeWithFont:font constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeCharacterWrap];
169     return stringSize.height;
170 }
171
172 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
173     CGFloat result = aTableView.rowHeight;
174     
175     if (indexPath.section == cdnURLSection) {
176         result = 22.0 + [self findLabelHeight:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] font:[UIFont systemFontOfSize:18.0]];
177     } else if (indexPath.section == kDetails && indexPath.row == 1) {
178         CGSize textLabelSize;
179         if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
180             textLabelSize = CGSizeMake(537.0, 9000.0f);
181         } else {
182             textLabelSize = CGSizeMake(221.0, 9000.0f);
183         }
184         CGSize stringSize = [object.fullPath sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
185         return 22.0 + stringSize.height;
186     } else if (indexPath.section == kDetails && indexPath.row == 0) {
187         CGSize textLabelSize;
188         if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
189             textLabelSize = CGSizeMake(537.0, 9000.0f);
190         } else {
191             textLabelSize = CGSizeMake(221.0, 9000.0f);
192         }
193         CGSize stringSize = [object.name sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
194         return 22.0 + stringSize.height;
195     }
196     
197     return MAX(aTableView.rowHeight, result);
198 }
199
200 - (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
201     
202     static NSString *CellIdentifier = @"Cell";
203     
204     UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
205     if (cell == nil) {
206         cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
207         //cell.backgroundColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.93];
208         cell.textLabel.backgroundColor = [UIColor clearColor];
209         cell.detailTextLabel.backgroundColor = [UIColor clearColor];
210         cell.detailTextLabel.numberOfLines = 0;
211         cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
212     }
213     
214     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
215         cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
216     }
217     
218     cell.detailTextLabel.textAlignment = UITextAlignmentRight;
219     
220     // Configure the cell...
221     if (indexPath.section == kDetails) {
222         cell.accessoryType = UITableViewCellAccessoryNone;
223         cell.selectionStyle = UITableViewCellSelectionStyleNone;
224         cell.accessoryView = nil;
225         if (indexPath.row == 0) {
226             cell.textLabel.text = @"Name";
227             cell.detailTextLabel.text = object.name;
228         } else if (indexPath.row == 1) {
229             cell.textLabel.text = @"Full Path";
230             cell.detailTextLabel.text = object.fullPath;
231         } else if (indexPath.row == 2) {
232             cell.textLabel.text = @"Size";
233             cell.detailTextLabel.text = [object humanizedBytes];
234         } else if (indexPath.row == 3) {
235             cell.textLabel.text = @"Type";
236             cell.detailTextLabel.text = object.contentType;
237         }
238     } else if (indexPath.section == kMetadata) {
239         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
240         cell.selectionStyle = UITableViewCellSelectionStyleBlue;
241         cell.accessoryView = nil;
242         if (indexPath.row == [object.metadata count]) {
243             cell.textLabel.text = @"Add Metadata";
244             cell.detailTextLabel.text = @"";
245         } else {
246             NSString *key = [[object.metadata allKeys] objectAtIndex:indexPath.row];
247             cell.textLabel.text = key;
248             cell.detailTextLabel.text = [object.metadata objectForKey:key];
249         }
250     } else if (indexPath.section == cdnURLSection) {
251         cell.detailTextLabel.textAlignment = UITextAlignmentLeft;
252         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
253         cell.textLabel.text = @"";
254         cell.detailTextLabel.text = [[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
255     } else if (indexPath.section == actionsSection) {
256         cell.accessoryView = nil;
257         if (performingAction) {
258             cell.textLabel.textColor = [UIColor grayColor];
259             cell.selectionStyle = UITableViewCellSelectionStyleNone;
260             cell.accessoryType = UITableViewCellAccessoryNone;
261         } else {
262             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
263             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
264         }
265
266         if (indexPath.row == 0) {
267             if (fileDownloaded) {
268                 cell.textLabel.text = @"Open File";
269             } else {
270                 if (fileDownloading) {
271                     cell.accessoryView = downloadProgressView;
272                     // TODO: if you leave this view while downloading, there's EXC_BAD_ACCESS
273                     cell.textLabel.text = @"Downloading";
274                 } else {
275                     cell.textLabel.text = @"Download File";
276                 }
277             }
278             cell.detailTextLabel.text = @"";
279             
280         } else if (indexPath.row == 1) {
281             cell.textLabel.text = @"Email File as Attachment";
282             cell.detailTextLabel.text = @"";
283         }
284     } else if (indexPath.section == deleteSection) {
285         cell.textLabel.text = @"Delete Object";
286         cell.detailTextLabel.text = @"";
287         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
288         cell.selectionStyle = UITableViewCellSelectionStyleBlue;
289     }
290     
291     return cell;
292 }
293
294
295 #pragma mark -
296 #pragma mark Table view delegate
297
298 - (void)reloadActionsTitleRow:(NSTimer *)timer {
299     [[timer.userInfo objectForKey:@"tableView"] reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
300 }
301
302 - (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
303     if (indexPath.section == cdnURLSection) {
304         [cdnURLActionSheet showInView:self.view];
305     } else if (indexPath.section == actionsSection) {
306         if (indexPath.row == 0) {
307             if (fileDownloaded) {
308                 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
309                 NSString *documentsDirectory = [paths objectAtIndex:0];        
310                 NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
311                 NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
312                                 
313                 UIDocumentInteractionController *vc = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
314                 vc.delegate = self;
315                 
316                 if (![vc presentPreviewAnimated:YES]) {
317                     
318                     if ([self.object isPlayableMedia]) {
319                         MediaViewController *vc = [[MediaViewController alloc] initWithNibName:@"MediaViewController" bundle:nil];
320                         vc.container = self.container;
321                         vc.object = self.object;
322                         [self.navigationController pushViewController:vc animated:YES];
323                         [vc release];
324                     } else {
325                         [self alert:@"Error" message:@"This file could not be opened."];
326                         [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
327                     }
328                 }
329                 
330                 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];                
331                 
332             } else {                
333                 if (!fileDownloading) {
334                     // download the file
335                     fileDownloading = YES;
336                     [self.account.manager getObject:self.container object:self.object downloadProgressDelegate:self];
337                     [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
338                 }
339                 
340             }
341         } else if (indexPath.row == 1) {
342             
343             NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
344             NSString *documentsDirectory = [paths objectAtIndex:0];        
345             NSString *shortPath = @"";
346             
347             if (self.container && [self.container respondsToSelector:@selector(name)] && self.object && [self.object respondsToSelector:@selector(fullPath)]) {
348                 shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
349             }
350             
351             NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
352             NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
353             
354             MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
355             vc.mailComposeDelegate = self;              
356             [vc setSubject:self.object.name];
357             [vc addAttachmentData:data mimeType:self.object.contentType fileName:self.object.name];
358             [vc setMessageBody:@"" isHTML:NO];    
359             if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
360                 vc.modalPresentationStyle = UIModalPresentationPageSheet;
361             }                
362             [self presentModalViewController:vc animated:YES];
363             [vc release];        
364         }
365     } else if (indexPath.section == deleteSection) {
366         [deleteActionSheet showInView:self.view];
367     }
368 }
369
370 - (void)setProgress:(float)newProgress {
371     [downloadProgressView setProgress:newProgress animated:YES];    
372     if (newProgress >= 1.0) {
373         fileDownloading = NO;
374         fileDownloaded = YES;
375         
376         [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:1 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationBottom];
377         [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
378     }
379 }
380
381 #pragma mark -
382 #pragma mark Document Interation Controller Delegate
383
384 - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *) controller {
385     return self.navigationController;
386 }
387
388 - (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controllers {
389     [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];
390 }
391
392 #pragma mark -
393 #pragma mark Action Sheet Delegate
394
395 - (void)deleteObjectRow {
396     [self.folderViewController.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:selectedIndexPath] withRowAnimation:UITableViewRowAnimationLeft];
397 }
398
399 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
400     if ([actionSheet isEqual:deleteActionSheet]) {
401         if (buttonIndex == 0) {
402             // delete the file and pop out
403             [self showToolbarActivityMessage:@"Deleting file..."];
404             
405             [self.account.manager deleteObject:self.container object:self.object];
406             
407             deleteSuccessObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"deleteObjectSucceeded" object:self.object
408                                                                                                queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
409             {
410                 [self hideToolbarActivityMessage];
411                 performingAction = NO;
412                 [self.folder.objects removeObjectForKey:self.object.name];
413                 [self.navigationController popViewControllerAnimated:YES];
414                 if ([self.folder.objects count] + [self.folder.folders count] == 0) {
415                     [self.folderViewController.tableView reloadData];
416                 } else {
417                     [self.folderViewController.tableView selectRowAtIndexPath:selectedIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
418                     [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(deleteObjectRow) userInfo:nil repeats:NO];
419                 }
420                 [[NSNotificationCenter defaultCenter] removeObserver:deleteSuccessObserver];
421             }];
422
423             deleteFailureObserver = [[NSNotificationCenter defaultCenter] addObserverForName:@"deleteObjectFailed" object:self.object
424                                                                                        queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification* notification) 
425             {
426                 [self hideToolbarActivityMessage];
427                 performingAction = NO;
428                 [self alert:@"There was a problem deleting this file." request:[notification.userInfo objectForKey:@"request"]];
429
430                 [[NSNotificationCenter defaultCenter] removeObserver:deleteFailureObserver];
431
432             }];
433             
434         }
435         NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:deleteSection];
436         [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
437     } else if ([actionSheet isEqual:cdnURLActionSheet]) {
438         NSURL *url = [NSURL URLWithString:[[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
439
440         if (buttonIndex == 0) {
441             // copy to pasteboard
442             UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
443             [pasteboard setString:[url description]];
444         } else if (buttonIndex == 1) {
445             // open in safari
446             UIApplication *application = [UIApplication sharedApplication];
447             if ([application canOpenURL:url]) {
448                 [application openURL:url];
449             } else {
450                 [self alert:@"Error" message:[NSString stringWithFormat:@"This URL cannot be opened.\n%@", url]];
451             }
452         } else if (buttonIndex == 2) {
453             // email link to file
454             MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
455             vc.mailComposeDelegate = self;              
456             [vc setSubject:self.object.name];
457             [vc setMessageBody:[url description] isHTML:NO];
458             if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
459                 vc.modalPresentationStyle = UIModalPresentationPageSheet;
460             }                
461             [self presentModalViewController:vc animated:YES];
462             [vc release];        
463         } else if (buttonIndex == 3) {
464             // tweet link to file
465             UIApplication *app = [UIApplication sharedApplication];            
466             NSURL *twitterURL = [NSURL URLWithString:[NSString stringWithFormat:@"twitter://post?message=%@", [[url description] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
467             if ([app canOpenURL:twitterURL]) {
468                 [app openURL:twitterURL];
469             }
470         }
471         [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:cdnURLSection] animated:YES];
472     }
473 }
474
475 #pragma mark -
476 #pragma mark Mail Composer Delegate
477
478 // Dismisses the email composition interface when users tap Cancel or Send.
479 - (void)mailComposeController:(MFMailComposeViewController*)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError*)error {  
480         [self dismissModalViewControllerAnimated:YES];
481     [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:actionsSection] animated:YES];
482 }
483
484 #pragma mark -
485 #pragma mark Memory management
486
487 - (void)dealloc {
488     [account release];
489     [downloadProgressView release];
490     [deleteActionSheet release];
491     [cdnURLActionSheet release];
492     [tableView release];
493     [folderViewController release];
494     [super dealloc];
495 }
496
497
498 @end
499