Show last modified date instead of content type in folder listing. Disable refresh...
[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
30 #define kDetails 0
31 #define kMetadata 2
32
33 #define maxMetadataViewableLength 12
34
35
36 // TODO: use etag to reset download
37 // TODO: try downloading directly to the file to save memory.  don't use object.data
38
39 /*
40  Name                           whatever.txt
41  Full Path
42  Size                           123 KB
43  Content Type                   text/plain
44  
45  Metadata
46  Key                            Value -> tap goes to a metadata item VC to edit or delete
47  Key                            Value
48  Add Metadata... (if max not already reached)
49  
50  CDN URL sections (action sheet to copy, open in safari, and email link)
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 (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
65 }
66
67 - (void)setBackgroundView {
68     
69     UIImageView *logo = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"cloudfiles-large.png"]];
70     
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);
77         logo.alpha = 0.5;        
78         [backgroundContainer addSubview:logo];
79         tableView.backgroundView = backgroundContainer;
80         [backgroundContainer release];
81     } else {        
82         self.tableView.backgroundView = nil;
83     }
84     [logo release];    
85 }
86
87 - (IBAction)homeButtonPressed:(id)sender {
88     [self.navigationController popToRootViewControllerAnimated:YES];
89 }
90
91 #pragma mark -
92 #pragma mark View lifecycle
93
94 - (void)viewDidLoad {
95     [super viewDidLoad];
96         
97     objectIsPublicSwitch = [[UISwitch alloc] init];
98     [objectIsPublicSwitch addTarget:self action:@selector(objectIsPublicSwitchChanged:) forControlEvents:UIControlEventValueChanged];
99     
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];
109                             
110             NSArray *users = [[array objectAtIndex:1] componentsSeparatedByString:@","];
111             for (NSString *user in users) {
112                 [permissions setObject:permissionsType forKey:user];
113             }
114         }
115     }
116     objectIsReadOnly = NO;
117     if (account.sharingAccount) { 
118         if ([permissions count] > 0) {
119             objectIsReadOnly = [[permissions objectForKey:[account username]] isEqualToString:@"read"];
120         }
121         objectIsPublicSwitch.enabled = NO;
122     }
123     
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];
130     }
131     appDelegate = [[UIApplication sharedApplication] delegate];
132 }
133
134 - (void)viewWillAppear:(BOOL)animated {
135     [super viewWillAppear:animated];
136     self.navigationItem.title = object.name;
137     
138     NSIndexPath *selection = [self.tableView indexPathForSelectedRow];
139         if (selection)
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;
144         else {
145             fileDownloaded = NO;
146             [appDelegate.cachedObjectsDictionary removeObjectForKey:object.hash];
147             [appDelegate saveCacheDictionary];
148         }
149     } else {
150         fileDownloaded = NO;
151     }
152     
153     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
154         CGRect rect = downloadProgressView.frame;
155         rect.size.width = 440.0;
156         downloadProgressView.frame = rect;
157     }
158     
159     //[self setBackgroundView];
160     actionsSection = 1;
161     publicLinkSection = 3;
162     permissionsSection = 4;
163     if (self.container.cdnEnabled) {
164         cdnURLSection = 5;
165         deleteSection = 6;
166         versionsSection = 7;
167     } else {
168         cdnURLSection = -1;
169         deleteSection = 5;
170         versionsSection = 6;
171     }
172     
173     if (versionID) {
174         publicLinkSection = -1;
175         permissionsSection = -1;
176         versionsSection = -1;
177         deleteSection = -1;
178         objectIsReadOnly = YES;
179     }
180     if (account.sharingAccount || account.shared) {
181         versionsSection = -1;
182         deleteSection = -1;
183     }
184     
185     
186     // let's see if we can tweet
187     UIApplication *app = [UIApplication sharedApplication];
188     NSURL *twitterURL = [NSURL URLWithString:@"twitter://post?message=test"];
189
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];
192     } else {
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];
194     }
195     objectIsPublic = ([object.publicURI length]);
196     objectIsPublicSwitch.on = objectIsPublic;
197     [self.tableView reloadData];
198 }
199
200 - (void)viewDidAppear:(BOOL)animated {
201     if (object.metadata == nil) {
202         [self reloadMetadataSection];
203     }
204 }
205
206 #pragma mark -
207 #pragma mark Table view data source
208
209 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
210
211     int numberOfSections = 8;
212     if (!self.container.cdnEnabled)
213         numberOfSections--;
214     if (deleteSection < 0)
215         numberOfSections--;
216     if (versionsSection < 0)
217         numberOfSections--;
218     if (publicLinkSection < 0)
219         numberOfSections--;
220     if (permissionsSection < 0)
221         numberOfSections--;
222
223     return numberOfSections;
224 }
225
226 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
227     if (section == kDetails) {
228         return 4;
229     } else if (section == kMetadata) {
230         if (objectIsReadOnly) {
231             return [object.metadata count];
232         } else {
233             return 1 + [object.metadata count];
234         }
235     } else if (section == cdnURLSection) {
236         return 1;
237     } else if (section == actionsSection) {
238         return 2;
239     } else if (section == publicLinkSection) {
240         return objectIsPublic ? 2 : 1;
241     } else if (section == permissionsSection) {
242         if (account.sharingAccount || objectIsReadOnly)
243             return [permissions count];
244         else
245             return 1 + [permissions count];
246     } else {
247         return 1;
248     }
249 }
250
251 - (CGFloat)findLabelHeight:(NSString*)text font:(UIFont *)font {
252     CGSize textLabelSize;    
253     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
254         // 616, 678
255         textLabelSize = CGSizeMake(596.0, 9000.0f);
256     } else {
257         textLabelSize = CGSizeMake(280.0, 9000.0f);
258     }
259     CGSize stringSize = [text sizeWithFont:font constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeCharacterWrap];
260     return stringSize.height;
261 }
262
263 - (CGFloat)tableView:(UITableView *)aTableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
264     CGFloat result = aTableView.rowHeight;
265
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);
272         } else {
273             textLabelSize = CGSizeMake(221.0, 9000.0f);
274         }
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);
281         } else {
282             textLabelSize = CGSizeMake(221.0, 9000.0f);
283         }
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];
290                                    
291         result = 30.0 + [self findLabelHeight:publicLinkUrl font:[UIFont systemFontOfSize:15.0]];
292     }
293     return MAX(aTableView.rowHeight, result);
294 }
295
296 - (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
297     
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];
303             if (cell == nil) {
304                 cell = [[[TextViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:TextViewCellIdentifier] autorelease];
305                 cell.textView.frame = cell.contentView.frame;
306
307             }
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:@"!*'();:@&=+$,?%#[]"]];
315             
316             cell.selectionStyle = UITableViewCellSelectionStyleNone;
317             cell.accessoryView = UITableViewCellAccessoryNone;
318             return cell;
319         }
320     }
321     
322     UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
323     if (cell == nil) {
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;
328     }
329     cell.textLabel.textColor = [UIColor blackColor];
330     cell.detailTextLabel.numberOfLines = 0;
331
332     if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
333         cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
334     }
335     cell.userInteractionEnabled = YES;
336     cell.detailTextLabel.textAlignment = UITextAlignmentRight;
337     cell.accessoryView = nil;
338     cell.textLabel.font = [UIFont boldSystemFontOfSize:17.0];
339     
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;
357         }
358     } else if (indexPath.section == kMetadata) {
359         if (objectIsReadOnly) {
360             cell.accessoryType = UITableViewCellAccessoryNone;
361             cell.selectionStyle = UITableViewCellSelectionStyleNone;
362             cell.userInteractionEnabled = NO;
363         }
364         else {
365             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
366             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
367         }
368         
369         cell.accessoryView = nil;
370         if (indexPath.row == [object.metadata count]) {
371             cell.textLabel.text = @"Add Metadata";
372             cell.detailTextLabel.text = @"";
373         } else {
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:@"..."];
381             }
382             if ([metadataValueCellText length] > maxMetadataViewableLength) {
383                 metadataValueCellText = [metadataValueCellText substringToIndex:(maxMetadataViewableLength - 3)];
384                 metadataValueCellText = [metadataValueCellText stringByAppendingString:@"..."];
385             }
386             
387             cell.textLabel.text = metadataKeyCellText;
388             cell.detailTextLabel.text = metadataValueCellText;
389         }
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;
397         }
398     } else if (indexPath.section == permissionsSection) {
399         if (account.sharingAccount) {
400             cell.accessoryType = UITableViewCellAccessoryNone;
401             cell.selectionStyle = UITableViewCellSelectionStyleNone;
402             cell.userInteractionEnabled = NO;
403         }
404         else {
405             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
406             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
407         }
408         cell.accessoryView = nil;
409         
410         if (indexPath.row == [permissions count]) {
411             cell.textLabel.text = @"Share";
412             cell.detailTextLabel.text = @""; 
413         } else {
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";
419             else
420                 accessType = @"Read Only";
421             cell.detailTextLabel.numberOfLines = 1;
422             cell.detailTextLabel.lineBreakMode = UILineBreakModeTailTruncation;
423             cell.detailTextLabel.text = accessType;
424         }
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;
434             } else {
435                 cell.textLabel.textColor = [UIColor grayColor];
436                 cell.selectionStyle = UITableViewCellSelectionStyleNone;
437             }
438         } else {
439             cell.selectionStyle = UITableViewCellSelectionStyleBlue;
440             cell.accessoryView = nil;
441         }
442         if (indexPath.row == 0) {
443             cell.textLabel.text = @"Open File"; 
444             cell.accessoryType = UITableViewCellAccessoryNone;
445             cell.detailTextLabel.text = @"";
446             
447         } else if (indexPath.row == 1) {
448             cell.textLabel.text = @"Email File as Attachment";
449             cell.detailTextLabel.text = @"";
450             cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
451         }        
452     } else if (indexPath.section == deleteSection) {
453         cell.selectionStyle = UITableViewCellSelectionStyleBlue;
454         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
455         cell.textLabel.text = @"Delete Object";
456         cell.detailTextLabel.text = @"";
457         
458     } else if (indexPath.section == versionsSection) {
459         cell.selectionStyle = UITableViewCellSelectionStyleBlue;
460         cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
461         cell.textLabel.text = @"Versions";
462         cell.detailTextLabel.text = @"";
463     }
464     
465     return cell;
466 }
467
468
469
470 #pragma mark -
471 #pragma mark Table view delegate
472
473 - (void)reloadActionsTitleRow:(NSTimer *)timer {
474     [[timer.userInfo objectForKey:@"tableView"] reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
475 }
476
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;
482         
483         if (indexPath.row == [self.object.metadata count]) {
484             metadataKey = @"";
485             metadataValue = @"";
486             vc.removeMetadataEnabled = FALSE;
487             vc.navigationItem.title = @"Add Metadata";
488         }
489         else {
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";
494         }
495
496         vc.metadataKey = metadataKey;
497         vc.metadataValue = metadataValue;
498         vc.account = account;
499         vc.container = container;
500         vc.object = object;
501         [self.navigationController pushViewController:vc animated:YES];
502         [vc release];
503     } else if (indexPath.section == permissionsSection) {
504         EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
505         NSString *user;
506         
507         if (indexPath.row == [permissions count]) {
508             user = @"";
509             vc.removePermissionsEnabled = NO;
510             vc.navigationItem.title = @"Share";
511         }
512         else {
513             user = [[permissions allKeys] objectAtIndex:indexPath.row];
514             NSString *userPermissions = [permissions objectForKey:user];
515             if ([userPermissions rangeOfString:@"read"].location != NSNotFound)
516                 vc.readPermissionSelected = YES;
517             else
518                 vc.readPermissionSelected = NO;
519             
520             if ([userPermissions rangeOfString:@"write"].location != NSNotFound)
521                 vc.writePermissionSelected = YES;
522             else
523                 vc.writePermissionSelected = NO;
524             
525             vc.removePermissionsEnabled = YES;
526             vc.navigationItem.title = @"Edit Sharing";
527         }
528         
529         vc.user = user;
530         vc.permissions = permissions;
531         vc.account = account;
532         vc.container = container;
533         vc.object = object;
534         vc.folderViewController = folderViewController;
535         [self.navigationController pushViewController:vc animated:YES];
536         [vc release];
537     } else if (indexPath.section == cdnURLSection) {
538         [cdnURLActionSheet showInView:self.view];
539     } else if (indexPath.section == actionsSection) {
540         if (!fileDownloaded) {
541             if (!fileDownloading) {
542                 // download the file
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];
555                  }
556                  failure:^(OpenStackRequest *request) {
557                      fileDownloaded = NO;
558                      fileDownloading = NO;
559                      [self alert:@"File failed to download" request:request];
560                      [self.tableView reloadData];
561                  }];
562                 [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexPath.row inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
563             }
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;
570
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];
582                         [vc release];
583                     } else {
584                         [self alert:@"Error" message:@"This file could not be opened."];
585                         [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
586                     }
587                 } 
588                 
589                 [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];                
590                 
591             } 
592         } else if (indexPath.row == 1) {
593             
594             NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
595             NSString *documentsDirectory = [paths objectAtIndex:0];        
596             NSString *shortPath = @"";
597             
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];
600             }
601             
602             NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
603             NSData *data = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:filePath]];
604             
605             
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];
609             }
610             else {
611                 MFMailComposeViewController *vc = [[MFMailComposeViewController alloc] init];
612             
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;
619                 }                
620                 [self presentModalViewController:vc animated:YES];
621                 [vc release];        
622             }
623         }
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;
630         vc.object = object;
631         [self.navigationController pushViewController:vc animated:YES];
632         [vc release];
633     }
634 }
635
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];
642     }
643 }
644
645
646 #pragma mark -
647 #pragma mark Document Interation Controller Delegate
648
649 - (UIViewController *)documentInteractionControllerViewControllerForPreview:(UIDocumentInteractionController *) controller {
650     return self.navigationController;
651 }
652
653 - (void)documentInteractionControllerDidEndPreview:(UIDocumentInteractionController *)controllers {
654     [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];
655 }
656
657 #pragma mark -
658 #pragma mark Action Sheet Delegate
659
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;
664 }
665
666 - (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
667     if ([actionSheet isEqual:deleteActionSheet]) {
668         if (buttonIndex == 0) {
669             // delete the file and pop out
670             
671             NSString *activityMessage = @"Deleting file";
672             [activityIndicatorView release];
673             activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
674             [activityIndicatorView addToView:self.view];
675
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];
683                      if (account.shared)
684                          self.folderViewController.needsRefreshing = YES;
685                  } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
686                      self.folderViewController.selectedObjectViewController = nil;
687                      if (!account.shared)
688                          [self.folderViewController setDetailViewController];
689                      else 
690                         [self.folderViewController refreshButtonPressed:nil];
691                  }
692                  if (self.folder.objects.count == 1)
693                      [self.folder.objects removeObjectForKey:self.object.name];
694                  
695                  if ([self.folder.objects count] + [self.folder.folders count] == 0) {
696                      [self.folderViewController.tableView reloadData];
697                  } else {
698                      [self.folderViewController.tableView selectRowAtIndexPath:selectedIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
699                      [NSTimer scheduledTimerWithTimeInterval:0.75 target:self selector:@selector(deleteObjectRow) userInfo:nil repeats:NO];
700                  }
701              }
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]; 
707              }];
708         }
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]];
713
714         if (buttonIndex == 0) {
715             // copy to pasteboard
716             UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
717             [pasteboard setString:[url description]];
718         } else if (buttonIndex == 1) {
719             // open in safari
720             UIApplication *application = [UIApplication sharedApplication];
721             if ([application canOpenURL:url]) {
722                 [application openURL:url];
723             } else {
724                 [self alert:@"Error" message:[NSString stringWithFormat:@"This URL cannot be opened.\n%@", url]];
725             }
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;
734             }                
735             [self presentModalViewController:vc animated:YES];
736             [vc release];        
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];
743             }
744         }
745         [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:cdnURLSection] animated:YES];
746     }
747 }
748
749 #pragma mark -
750 #pragma mark Mail Composer Delegate
751
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];
756 }
757
758 #pragma mark -
759 #pragma mark Switches
760
761 - (void)objectIsPublicSwitchChanged:(id)sender
762 {
763     NSString *activityMessage = [NSString stringWithFormat:@"Enabling public link.."];
764     self.oldPubicURI = object.publicURI;
765     
766     if (objectIsPublic) {
767         activityMessage = [NSString stringWithFormat:@"Disabling public link.."];
768         object.publicURI = @"";
769     }
770     else {
771         object.publicURI = @"TRUE";
772     }
773     [activityIndicatorView release];
774     activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
775     [activityIndicatorView addToView:self.view scrollOffset:self.tableView.contentOffset.y];    
776     
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];
789               }
790               failure:^(OpenStackRequest *request) {
791                   [activityIndicatorView removeFromSuperview];
792                   [self alert:@"There was a problem retrieving the public link from the server." request:request]; 
793               }];
794          }
795          else {
796              [activityIndicatorView removeFromSuperview];
797              [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
798                                    withRowAnimation:UITableViewRowAnimationTop];
799          }
800      }
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];           
807
808      }];    
809 }
810
811
812
813 #pragma mark -
814 #pragma mark Memory management
815
816 - (void)dealloc {
817     if (fileDownloading) {
818         OpenStackRequest *request = [self.account.manager.objectDownloadRequests objectForKey:object.fullPath];
819         [request setDownloadProgressDelegate:nil];
820     }
821     [account release];
822     [downloadProgressView release];
823     [deleteActionSheet release];
824     [cdnURLActionSheet release];
825     [tableView release];
826     [folderViewController release];
827     [objectIsPublicSwitch release];
828     [permissions release];
829     [documentInteractionController release];
830     [actionSelectedIndexPath release];
831     [versionID release];
832     [activityIndicatorView release];
833     [super dealloc];
834 }
835
836 #pragma mark -
837 #pragma mark Helper functions
838
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];
855              }
856          }   
857          NSIndexSet *metadataSections = [NSIndexSet indexSetWithIndex:kMetadata];
858          [self.tableView reloadSections:metadataSections withRowAnimation:UITableViewRowAnimationFade];
859      }
860      failure:^(OpenStackRequest *request) {
861          [activityIndicatorView removeFromSuperview];
862          [self alert:@"There was a problem retrieving the object's metadata." request:request]; 
863      }];
864 }
865
866
867 @end
868