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