Revision 64929bae

b/Classes/AccountManager.h
16 16
@interface AccountManager : NSObject {
17 17
    OpenStackAccount *account;
18 18
    ASINetworkQueue *queue;
19
    NSMutableDictionary *objectDownloadRequests;
19 20
}
20 21

  
21 22
@property (nonatomic, retain) ASINetworkQueue *queue;
22 23

  
23 24
@property (nonatomic, assign) OpenStackAccount *account;
25
@property (nonatomic, retain) NSMutableDictionary *objectDownloadRequests;
24 26

  
25 27
- (NSString *)notificationName:(NSString *)key identifier:(NSString *)identifier;
26 28
- (void)notify:(NSString *)name request:(OpenStackRequest *)request;
......
61 63
- (void)getObjects:(Container *)container;
62 64
- (void)getObjects:(Container *)container afterMarker:(NSString *)marker objectsBuffer:(NSMutableDictionary *)objectsBuffer;
63 65
- (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object;
66
- (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object version:(NSString *)version;
67
- (APICallback *)getObjectVersionsList:(Container *)container object:(StorageObject *)object;
64 68
- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate;
69

  
70
- (APICallback *)getObject:(Container *)container
71
                    object:(StorageObject *)object
72
  downloadProgressDelegate:(id)downloadProgressDelegate
73
           requestUserInfo:(NSDictionary *)requestUserInfo;
74

  
75
- (APICallback *)getObject:(Container *)container
76
                    object:(StorageObject *)object
77
  downloadProgressDelegate:(id)downloadProgressDelegate 
78
           requestUserInfo:(NSDictionary *)requestUserInfo
79
                   version:(NSString *)version;
80

  
81

  
65 82
- (APICallback *)writeObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate;
66 83
- (APICallback *)writeObjectMetadata:(Container *)container object:(StorageObject *)object;
67 84
- (APICallback *)deleteObject:(Container *)container object:(StorageObject *)object;
b/Classes/AccountManager.m
27 27
#import "APICallback.h"
28 28
#import "Analytics.h"
29 29
#import "JSON.h"
30
#import "OpenStackAppDelegate.h"
30 31

  
31 32

  
32 33

  
33 34

  
34 35
@implementation AccountManager
35 36

  
36
@synthesize account, queue;
37
@synthesize account, queue, objectDownloadRequests;
37 38

  
38 39
#pragma mark - Callbacks
39 40

  
......
494 495
    return [self callbackWithRequest:request];
495 496
}
496 497

  
498
- (APICallback *)getObjectInfo:(Container *)container object:(StorageObject *)object version:(NSString *)version {
499
    if (!version)
500
        return [self getObjectInfo:container object:object];
501
    
502
    __block OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:self.account container:container object:object version:version];
503
    return [self callbackWithRequest:request];
504
}
505

  
506
- (APICallback *)getObjectVersionsList:(Container *)container object:(StorageObject *)object {
507
    __block OpenStackRequest *request = [OpenStackRequest getObjectVersionsRequest:account container:container object:object];
508
    return [self callbackWithRequest:request];
509
}
510

  
497 511
- (APICallback *)getObject:(Container *)container object:(StorageObject *)object downloadProgressDelegate:(id)downloadProgressDelegate {
498
    __block OpenStackRequest *request = [OpenStackRequest getObjectRequest:self.account container:container object:object];
512
    return [self getObject:container object:object downloadProgressDelegate:downloadProgressDelegate requestUserInfo:nil];
513
}
514

  
515
- (APICallback *)getObject:(Container *)container
516
                    object:(StorageObject *)object
517
  downloadProgressDelegate:(id)downloadProgressDelegate
518
           requestUserInfo:(NSDictionary *)requestUserInfo {
519
    
520
    return [self getObject:container object:object downloadProgressDelegate:downloadProgressDelegate requestUserInfo:requestUserInfo version:nil];
521
}
522

  
523

  
524
- (APICallback *)getObject:(Container *)container
525
                    object:(StorageObject *)object
526
  downloadProgressDelegate:(id)downloadProgressDelegate 
527
           requestUserInfo:(NSDictionary *)requestUserInfo
528
                   version:(NSString *)version{
529
    __block OpenStackRequest *request = [OpenStackRequest getObjectRequest:self.account container:container object:object version:version];
499 530
    request.delegate = self;
500 531
    request.downloadProgressDelegate = downloadProgressDelegate;
501
    request.showAccurateProgress = YES;    
532
    request.showAccurateProgress = YES;  
533
    if (requestUserInfo) {
534
        request.userInfo = requestUserInfo;
535
    }
536
    if (!objectDownloadRequests)
537
        self.objectDownloadRequests = [NSMutableDictionary dictionary];
538
    [objectDownloadRequests setObject:request forKey:object.fullPath];
502 539
    return [self callbackWithRequest:request success:^(OpenStackRequest *request) {
503 540
        
504
        NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
505
        NSString *documentsDirectory = [paths objectAtIndex:0];        
506
        NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", container.name, object.fullPath];
507
        NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
508
        NSString *directoryPath = [filePath stringByReplacingOccurrencesOfString:[NSString stringWithFormat:@"/%@", object.name] withString:@""];
509
        
510
        NSFileManager *fileManager = [NSFileManager defaultManager];
511
        if ([fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:nil]) {
512
            
513
            [[request responseData] writeToFile:filePath atomically:YES];
514
            
541
        OpenStackAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
542
        NSString *filePath = [appDelegate.cacheDirectoryPath stringByAppendingFormat:@"/%@.%@",object.hash, object.name.pathExtension];
543
        [[request responseData] writeToFile:filePath atomically:YES];
544
        @synchronized(appDelegate.cachedObjectsDictionary) {
545
            [appDelegate.cachedObjectsDictionary setObject:filePath forKey:object.hash];
546
            [appDelegate saveCacheDictionary];
515 547
        }
516

  
548
        [objectDownloadRequests removeObjectForKey:object.fullPath];
549
    }
550
    failure:^(OpenStackRequest *request) { 
551
        [objectDownloadRequests removeObjectForKey:object.fullPath];
517 552
    }];
518 553
}
519 554

  
......
740 775
#pragma mark Memory Management
741 776

  
742 777
- (void)dealloc {
778
    [objectDownloadRequests release];
743 779
    [super dealloc];
744 780
}
745 781

  
b/Classes/FolderViewController.m
26 26
#import "OpenStackRequest.h"
27 27
#import "JSON.h"
28 28
#import "APICallback.h"
29
#import "OpenStackAppDelegate.h"
29 30

  
30 31

  
31 32
@implementation FolderViewController
......
236 237
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
237 238
            
238 239
            StorageObject *object = item;
239
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
240
            NSString *documentsDirectory = [paths objectAtIndex:0];
241
            NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, object.fullPath];
242
            NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
243

  
240
            OpenStackAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
241
            NSString *filePath = [appDelegate.cachedObjectsDictionary objectForKey:object.hash];
244 242
            if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
245 243
                UIDocumentInteractionController *udic = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
246 244
                if ([udic.icons count] > 0) {
b/Classes/ObjectVersionsViewController.h
1
//
2
//  ObjectVersionsViewController.h
3
//  pithos-ios
4
//
5
// Copyright 2011 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
15
//   2. Redistributions in binary form must reproduce the above
16
//      copyright notice, this list of conditions and the following
17
//      disclaimer in the documentation and/or other materials
18
//      provided with the distribution.
19
// 
20
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
// POSSIBILITY OF SUCH DAMAGE.
32
// 
33
// The views and conclusions contained in the software and
34
// documentation are those of the authors and should not be
35
// interpreted as representing official policies, either expressed
36
// or implied, of GRNET S.A.
37

  
38
#import <UIKit/UIKit.h>
39
#import "OpenStackViewController.h"
40

  
41
@class OpenStackAccount, Container, StorageObject;
42

  
43
@interface ObjectVersionsViewController : OpenStackViewController <UITableViewDelegate, UITableViewDataSource> {
44
    
45
    IBOutlet UITableView *tableView;
46
    NSMutableArray *versions;
47
    OpenStackAccount *account;
48
    Container *container;
49
    StorageObject *object;
50
    BOOL versionsLoaded;
51
    
52
}
53

  
54
@property (nonatomic, retain) IBOutlet UITableView *tableView;
55
@property (nonatomic, retain) NSMutableArray *versions;
56
@property (nonatomic, retain) OpenStackAccount *account;
57
@property (nonatomic, retain) Container *container;
58
@property (nonatomic, retain) StorageObject *object;
59

  
60
@end
b/Classes/ObjectVersionsViewController.m
1
//
2
//  ObjectVersionsViewController.m
3
//  pithos-ios
4
//
5
// Copyright 2011 GRNET S.A. All rights reserved.
6
//
7
// Redistribution and use in source and binary forms, with or
8
// without modification, are permitted provided that the following
9
// conditions are met:
10
// 
11
//   1. Redistributions of source code must retain the above
12
//      copyright notice, this list of conditions and the following
13
//      disclaimer.
14
// 
15
//   2. Redistributions in binary form must reproduce the above
16
//      copyright notice, this list of conditions and the following
17
//      disclaimer in the documentation and/or other materials
18
//      provided with the distribution.
19
// 
20
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31
// POSSIBILITY OF SUCH DAMAGE.
32
// 
33
// The views and conclusions contained in the software and
34
// documentation are those of the authors and should not be
35
// interpreted as representing official policies, either expressed
36
// or implied, of GRNET S.A.
37

  
38
#import "ObjectVersionsViewController.h"
39
#import "StorageObjectViewController.h"
40
#import "OpenStackAccount.h"
41
#import "OpenStackRequest.h"
42
#import "AccountManager.h"
43
#import "APICallback.h"
44
#import "JSON.h"
45
#import "ActivityIndicatorView.h"
46
#import "UIViewController+Conveniences.h"
47
#import "StorageObject.h"
48

  
49

  
50
@implementation ObjectVersionsViewController
51

  
52
@synthesize tableView, versions, account, container, object;
53

  
54
#pragma mark - View lifecycle
55

  
56
- (void)viewDidLoad {
57
    [super viewDidLoad];
58
    self.navigationItem.title = @"Versions";
59
    self.versions = [NSMutableArray array];
60
    versionsLoaded = NO;
61
    NSString *activityMessage = @"Loading..";
62
    ActivityIndicatorView *activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
63
    [activityIndicatorView addToView:self.view];
64

  
65
    [[self.account.manager  getObjectVersionsList:container object:object]
66
     success:^(OpenStackRequest *request) {
67
         SBJSON *parser = [[SBJSON alloc] init];
68
         NSArray *versionsInJson = [[parser objectWithString:[request responseString]] objectForKey:@"versions"];
69
         for (NSArray *versionInfo in versionsInJson) {
70
             [versions addObject:[NSDictionary dictionaryWithObjectsAndKeys:[versionInfo objectAtIndex:0], @"versionID",
71
                                 [NSDate dateWithTimeIntervalSince1970:[[versionInfo objectAtIndex:1] doubleValue]], @"versionDate",
72
                                 nil]]; 
73
         }
74
         [activityIndicatorView removeFromSuperviewAndRelease];
75
         versionsLoaded = YES;
76
         [self.tableView reloadData];
77
     }
78
     failure:^(OpenStackRequest *request) {
79
         [activityIndicatorView removeFromSuperviewAndRelease];
80
         [self alert:@"Couldn't get versions from server" request:request];
81
     }];    
82
}
83

  
84
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
85
    return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
86
}
87

  
88
#pragma mark - memory management
89

  
90
- (void)dealloc {
91
    [account release];
92
    [container release];
93
    [object release];
94
    [versions release];
95
    [super dealloc];
96
}
97

  
98

  
99
#pragma mark - Table view data source
100

  
101
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
102
    return 1;
103
}
104

  
105
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
106
    if (!versionsLoaded)
107
        return 0;
108
    else
109
        return MAX(1, versions.count);
110
}
111

  
112
- (UITableViewCell *)tableView:(UITableView *)aTableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
113
    static NSString *CellIdentifier = @"Cell";
114
    
115
    UITableViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:CellIdentifier];
116
    if (cell == nil) {
117
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
118
    }
119
    cell.userInteractionEnabled = YES;
120
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
121
    cell.selectionStyle = UITableViewCellSelectionStyleBlue;
122
    if (versions.count == 0 || !versions) {
123
        cell.textLabel.text = @"No previous versions available";
124
        cell.userInteractionEnabled = NO;
125
        cell.accessoryType = UITableViewCellAccessoryNone;
126
        cell.selectionStyle = UITableViewCellSelectionStyleNone;
127
    } else {
128
        NSDictionary *versionDetails = [versions objectAtIndex:indexPath.row];
129
        cell.textLabel.text = [[versionDetails objectForKey:@"versionID"] description];
130
        NSString *dateString = [NSDateFormatter localizedStringFromDate:[versionDetails objectForKey:@"versionDate"]
131
                                                              dateStyle:NSDateFormatterMediumStyle
132
                                                              timeStyle:NSDateFormatterMediumStyle];
133
        cell.detailTextLabel.text = dateString;
134
    }
135
    
136
    return cell;
137
}
138

  
139
#pragma mark - Table view delegate
140

  
141
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
142
    NSString *activityMessage = @"Loading..";
143
    ActivityIndicatorView *activityIndicatorView = [[ActivityIndicatorView alloc] initWithFrame:[ActivityIndicatorView frameForText:activityMessage] text:activityMessage];
144
    [activityIndicatorView addToView:self.view];
145
    
146
    NSDictionary *versionDetails = [versions objectAtIndex:indexPath.row];
147
    NSString *versionID = [[versionDetails objectForKey:@"versionID"] description];
148
    
149
    [[self.account.manager getObjectInfo:container object:object version:versionID]
150
     success:^(OpenStackRequest *request) {
151
         [activityIndicatorView removeFromSuperviewAndRelease];
152
         StorageObject *versionedObject = [[[StorageObject alloc] init] autorelease];
153
         versionedObject.name = object.name;
154
         versionedObject.fullPath = object.fullPath;
155
         [versionedObject setPropertiesfromResponseHeaders:request.responseHeaders];
156
         StorageObjectViewController *vc = [[StorageObjectViewController alloc] initWithNibName:@"StorageObjectViewController" bundle:nil];
157
         vc.objectIsReadOnly = YES;
158
         vc.account = account;
159
         vc.container = container;
160
         vc.object = versionedObject;
161
         vc.versionID = versionID;
162
         [self.navigationController pushViewController:vc animated:YES];
163
         [vc release];
164
         [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
165
     } 
166
     failure:^(OpenStackRequest *request) {
167
         [activityIndicatorView removeFromSuperviewAndRelease];
168
         [self alert:[NSString stringWithFormat:@"Failed to get file info for version: %@", versionID] request:request];
169
     }];
170
}
171

  
172
@end
b/Classes/ObjectVersionsViewController.xib
1
<?xml version="1.0" encoding="UTF-8"?>
2
<archive type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="8.00">
3
	<data>
4
		<int key="IBDocument.SystemTarget">1280</int>
5
		<string key="IBDocument.SystemVersion">11C74</string>
6
		<string key="IBDocument.InterfaceBuilderVersion">1938</string>
7
		<string key="IBDocument.AppKitVersion">1138.23</string>
8
		<string key="IBDocument.HIToolboxVersion">567.00</string>
9
		<object class="NSMutableDictionary" key="IBDocument.PluginVersions">
10
			<string key="NS.key.0">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
11
			<string key="NS.object.0">933</string>
12
		</object>
13
		<array key="IBDocument.IntegratedClassDependencies">
14
			<string>IBProxyObject</string>
15
			<string>IBUIView</string>
16
			<string>IBUITableView</string>
17
		</array>
18
		<array key="IBDocument.PluginDependencies">
19
			<string>com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
20
		</array>
21
		<object class="NSMutableDictionary" key="IBDocument.Metadata">
22
			<string key="NS.key.0">PluginDependencyRecalculationVersion</string>
23
			<integer value="1" key="NS.object.0"/>
24
		</object>
25
		<array class="NSMutableArray" key="IBDocument.RootObjects" id="1000">
26
			<object class="IBProxyObject" id="372490531">
27
				<string key="IBProxiedObjectIdentifier">IBFilesOwner</string>
28
				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
29
			</object>
30
			<object class="IBProxyObject" id="975951072">
31
				<string key="IBProxiedObjectIdentifier">IBFirstResponder</string>
32
				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
33
			</object>
34
			<object class="IBUIView" id="191373211">
35
				<reference key="NSNextResponder"/>
36
				<int key="NSvFlags">274</int>
37
				<array class="NSMutableArray" key="NSSubviews">
38
					<object class="IBUITableView" id="360754307">
39
						<reference key="NSNextResponder" ref="191373211"/>
40
						<int key="NSvFlags">274</int>
41
						<string key="NSFrameSize">{320, 460}</string>
42
						<reference key="NSSuperview" ref="191373211"/>
43
						<string key="NSReuseIdentifierKey">_NS:418</string>
44
						<object class="NSColor" key="IBUIBackgroundColor">
45
							<int key="NSColorSpace">3</int>
46
							<bytes key="NSWhite">MQA</bytes>
47
						</object>
48
						<bool key="IBUIClipsSubviews">YES</bool>
49
						<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
50
						<bool key="IBUIAlwaysBounceVertical">YES</bool>
51
						<int key="IBUISeparatorStyle">1</int>
52
						<int key="IBUISectionIndexMinimumDisplayRowCount">0</int>
53
						<bool key="IBUIShowsSelectionImmediatelyOnTouchBegin">YES</bool>
54
						<float key="IBUIRowHeight">44</float>
55
						<float key="IBUISectionHeaderHeight">22</float>
56
						<float key="IBUISectionFooterHeight">22</float>
57
					</object>
58
				</array>
59
				<string key="NSFrame">{{0, 20}, {320, 460}}</string>
60
				<reference key="NSSuperview"/>
61
				<reference key="NSNextKeyView"/>
62
				<object class="NSColor" key="IBUIBackgroundColor">
63
					<int key="NSColorSpace">3</int>
64
					<bytes key="NSWhite">MQA</bytes>
65
					<object class="NSColorSpace" key="NSCustomColorSpace">
66
						<int key="NSID">2</int>
67
					</object>
68
				</object>
69
				<object class="IBUISimulatedStatusBarMetrics" key="IBUISimulatedStatusBarMetrics"/>
70
				<string key="targetRuntimeIdentifier">IBCocoaTouchFramework</string>
71
			</object>
72
		</array>
73
		<object class="IBObjectContainer" key="IBDocument.Objects">
74
			<array class="NSMutableArray" key="connectionRecords">
75
				<object class="IBConnectionRecord">
76
					<object class="IBCocoaTouchOutletConnection" key="connection">
77
						<string key="label">view</string>
78
						<reference key="source" ref="372490531"/>
79
						<reference key="destination" ref="191373211"/>
80
					</object>
81
					<int key="connectionID">3</int>
82
				</object>
83
				<object class="IBConnectionRecord">
84
					<object class="IBCocoaTouchOutletConnection" key="connection">
85
						<string key="label">tableView</string>
86
						<reference key="source" ref="372490531"/>
87
						<reference key="destination" ref="360754307"/>
88
					</object>
89
					<int key="connectionID">5</int>
90
				</object>
91
				<object class="IBConnectionRecord">
92
					<object class="IBCocoaTouchOutletConnection" key="connection">
93
						<string key="label">dataSource</string>
94
						<reference key="source" ref="360754307"/>
95
						<reference key="destination" ref="372490531"/>
96
					</object>
97
					<int key="connectionID">6</int>
98
				</object>
99
				<object class="IBConnectionRecord">
100
					<object class="IBCocoaTouchOutletConnection" key="connection">
101
						<string key="label">delegate</string>
102
						<reference key="source" ref="360754307"/>
103
						<reference key="destination" ref="372490531"/>
104
					</object>
105
					<int key="connectionID">7</int>
106
				</object>
107
			</array>
108
			<object class="IBMutableOrderedSet" key="objectRecords">
109
				<array key="orderedObjects">
110
					<object class="IBObjectRecord">
111
						<int key="objectID">0</int>
112
						<array key="object" id="0"/>
113
						<reference key="children" ref="1000"/>
114
						<nil key="parent"/>
115
					</object>
116
					<object class="IBObjectRecord">
117
						<int key="objectID">1</int>
118
						<reference key="object" ref="191373211"/>
119
						<array class="NSMutableArray" key="children">
120
							<reference ref="360754307"/>
121
						</array>
122
						<reference key="parent" ref="0"/>
123
					</object>
124
					<object class="IBObjectRecord">
125
						<int key="objectID">-1</int>
126
						<reference key="object" ref="372490531"/>
127
						<reference key="parent" ref="0"/>
128
						<string key="objectName">File's Owner</string>
129
					</object>
130
					<object class="IBObjectRecord">
131
						<int key="objectID">-2</int>
132
						<reference key="object" ref="975951072"/>
133
						<reference key="parent" ref="0"/>
134
					</object>
135
					<object class="IBObjectRecord">
136
						<int key="objectID">4</int>
137
						<reference key="object" ref="360754307"/>
138
						<reference key="parent" ref="191373211"/>
139
					</object>
140
				</array>
141
			</object>
142
			<dictionary class="NSMutableDictionary" key="flattenedProperties">
143
				<string key="-1.CustomClassName">ObjectVersionsViewController</string>
144
				<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
145
				<string key="-2.CustomClassName">UIResponder</string>
146
				<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
147
				<string key="1.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
148
				<string key="4.IBPluginDependency">com.apple.InterfaceBuilder.IBCocoaTouchPlugin</string>
149
			</dictionary>
150
			<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
151
			<nil key="activeLocalization"/>
152
			<dictionary class="NSMutableDictionary" key="localizations"/>
153
			<nil key="sourceID"/>
154
			<int key="maxID">7</int>
155
		</object>
156
		<object class="IBClassDescriber" key="IBDocument.Classes"/>
157
		<int key="IBDocument.localizationMode">0</int>
158
		<string key="IBDocument.TargetRuntimeIdentifier">IBCocoaTouchFramework</string>
159
		<bool key="IBDocument.PluginDeclaredDependenciesTrackSystemTargetVersion">YES</bool>
160
		<int key="IBDocument.defaultPropertyAccessControl">3</int>
161
		<string key="IBCocoaTouchPluginVersion">933</string>
162
	</data>
163
</archive>
b/Classes/OpenStackAppDelegate.h
10 10

  
11 11
@class RootViewController;
12 12

  
13
#define CACHED_OBJECTS_ARCHIVE_KEY @"cachedObjectsDictionary"
14
#define CACHE_DICT_ARCHIVE_NAME @"cache.dict"
15

  
13 16
@interface OpenStackAppDelegate : NSObject <UIApplicationDelegate> {
14 17
    UIWindow *window;
15 18
    UINavigationController *navigationController;
......
18 21
    UIBarButtonItem *barButtonItem;
19 22
    RootViewController *rootViewController;
20 23
    id serviceUnavailableObserver;
24
    NSMutableDictionary *cachedObjectsDictionary;
25
    NSString *cacheDictionaryFilePath;
26
    NSString *cacheDirectoryPath;
21 27
    
28
    NSUserDefaults *userDefaults;
22 29
}
23 30

  
24 31
@property (nonatomic, retain) IBOutlet UIWindow *window;
......
27 34
@property (nonatomic, retain) UINavigationController *masterNavigationController;
28 35
@property (nonatomic, retain) UIBarButtonItem *barButtonItem;
29 36
@property (nonatomic, retain) RootViewController *rootViewController;
37
@property (nonatomic, retain) NSMutableDictionary *cachedObjectsDictionary;
38
@property (nonatomic, copy) NSString *cacheDictionaryFilePath;
39
@property (nonatomic, copy) NSString *cacheDirectoryPath;
30 40

  
31 41
//- (void) setupDependencies;
42
- (void)saveCacheDictionary;
32 43

  
33 44
@end
34 45

  
b/Classes/OpenStackAppDelegate.m
39 39
@synthesize masterNavigationController;
40 40
@synthesize barButtonItem;
41 41
@synthesize rootViewController;
42
@synthesize cachedObjectsDictionary;
43
@synthesize cacheDictionaryFilePath;
44
@synthesize cacheDirectoryPath;
42 45

  
43 46
- (void)loadSettingsDefaults {
44 47
    
......
57 60
        [Keychain setString:@"NO" forKey:@"passcode_lock_erase_data_on"];
58 61
    }
59 62
    
60
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
63
    userDefaults = [[NSUserDefaults standardUserDefaults] retain];
61 64
    
62
    if (![defaults stringForKey:@"api_logging_level"]) {
63
        [defaults setValue:@"all" forKey:@"api_logging_level"];
65
    if (![userDefaults stringForKey:@"api_logging_level"]) {
66
        [userDefaults setValue:@"all" forKey:@"api_logging_level"];
64 67
    }
65 68
    
66
    if (![defaults stringForKey:@"aboutURL"]) {
67
        [defaults setValue:@"https://plus.pithos.grnet.gr" forKey:@"aboutURL"];
68
    } 
69
    if (![userDefaults stringForKey:@"aboutURL"]) {
70
        [userDefaults setValue:@"https://plus.pithos.grnet.gr" forKey:@"aboutURL"];
71
    }
72
    
73
    self.cachedObjectsDictionary = [userDefaults objectForKey:@"cachedObjectsDictionary"];
74
    if (!cachedObjectsDictionary) {
75
        self.cachedObjectsDictionary = [NSMutableDictionary dictionary];
76
        [userDefaults setValue:cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
77
    }
69 78
    
70
    [defaults synchronize];
79
    [userDefaults synchronize];
71 80
    
72 81
}
73 82

  
......
148 157
        [alert release];
149 158
        [[NSNotificationCenter defaultCenter] removeObserver:serviceUnavailableObserver];
150 159
    }];
151
        
160
    
161
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
162
    self.cacheDirectoryPath = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"DownloadedFiles"];
163
    NSFileManager *fileManager = [NSFileManager defaultManager];
164
    NSError *error = nil;
165
    [fileManager createDirectoryAtPath:cacheDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
166
    if (error) {
167
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error"
168
                                                        message:[NSString stringWithFormat:@"Error in creating cache directory\n%@",error.localizedDescription]
169
                                                       delegate:nil 
170
                                              cancelButtonTitle:@"OK" otherButtonTitles:nil];
171
        [alert show];
172
        [alert release];
173
    }
174

  
152 175
    return YES;
153 176
}
154 177

  
......
202 225

  
203 226

  
204 227
- (void)applicationDidBecomeActive:(UIApplication *)application {
205
    /*
206
     Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
207
     */
208
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
209
    [defaults setBool:NO forKey:@"already_failed_on_connection"];
210
    [defaults synchronize];
228
    
229
    /* 
230
	 Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
231
	*/
232
    [userDefaults setBool:NO forKey:@"already_failed_on_connection"];
233
    [userDefaults synchronize];
211 234
    
212 235
    [self showPasscodeLock];
213 236
    //DispatchAnalytics();
......
291 314
    return YES;
292 315
}
293 316

  
317
#pragma mark - persistence
318

  
319
- (void)saveCacheDictionary {
320
    [userDefaults setObject:cachedObjectsDictionary forKey:@"cachedObjectsDictionary"];
321
    [userDefaults synchronize];
322
}
323

  
294 324

  
295 325
#pragma mark -
296 326
#pragma mark Memory management
......
309 339
    [barButtonItem release];
310 340
    [rootViewController release];
311 341
	[window release];
342
    [cachedObjectsDictionary release];
343
    [userDefaults release];
312 344
	[super dealloc];
313 345
}
314 346

  
b/Classes/OpenStackRequest.h
121 121
- (NSMutableDictionary *)objects;
122 122

  
123 123
+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
124
+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account
125
                                 container:(Container *)container
126
                                    object:(StorageObject *)object
127
                                   version:(NSString *)version;
128
+ (OpenStackRequest *)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
124 129
+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
130
+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account
131
                             container:(Container *)container
132
                                object:(StorageObject *)object
133
                               version:(NSString *)version;
125 134
+ (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
126 135
+ (OpenStackRequest *)writeObjectMetadataRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
127 136
+ (OpenStackRequest *)deleteObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object;
b/Classes/OpenStackRequest.m
661 661
    return [OpenStackRequest filesRequest:account method:@"HEAD" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
662 662
}
663 663

  
664
+ (OpenStackRequest *)getObjectInfoRequest:(OpenStackAccount *)account
665
                                 container:(Container *)container
666
                                    object:(StorageObject *)object
667
                                   version:(NSString *)version {
668
    OpenStackRequest *request = [OpenStackRequest getObjectInfoRequest:account container:container object:object];
669
    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
670
    return request;
671
}
672

  
673
+ (OpenStackRequest *)getObjectVersionsRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
674
    OpenStackRequest *request = [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
675
    
676
    request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=list",request.url.description]];
677
    return request;
678
}
679

  
664 680
+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
665 681
    return [OpenStackRequest filesRequest:account method:@"GET" path:[NSString stringWithFormat:@"/%@/%@",[NSString encodeToPercentEscape:container.name], [NSString encodeToPercentEscape:object.fullPath charactersToEncode:@"!*'();:@&=+$,?%#[]"]]];    
666 682
}
667 683

  
684
+ (OpenStackRequest *)getObjectRequest:(OpenStackAccount *)account
685
                             container:(Container *)container
686
                                object:(StorageObject *)object
687
                               version:(NSString *)version {
688
    OpenStackRequest *request = [OpenStackRequest getObjectRequest:account container:container object:object];
689
    if (version) {
690
        request.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@&version=%@",request.url.description, version]];
691
    }
692
    return request;
693
}
694

  
668 695
+ (OpenStackRequest *)writeObjectRequest:(OpenStackAccount *)account container:(Container *)container object:(StorageObject *)object {
669 696
    NSString *fullPath = object.fullPath;
670 697
    if ([fullPath characterAtIndex:0] == '/') {
b/Classes/PithosUtilities.h
40 40
@interface PithosUtilities : NSObject
41 41

  
42 42
+ (BOOL)isContentTypeDirectory:(NSString *)contentType;
43
+ (unsigned long long)sizeOfDirectory:(NSString *)directoryPath;
44
+ (NSString *)humanReadableSize:(unsigned long long)bytes;
43 45

  
44 46
@end
b/Classes/PithosUtilities.m
47 47
            [contentType hasPrefix:@"application/folder;"]);
48 48
}
49 49

  
50
+ (unsigned long long)sizeOfDirectory:(NSString *)directoryPath {
51
    NSFileManager *fileManager = [NSFileManager defaultManager];
52
    NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtPath:directoryPath];
53
    unsigned long long directorySize = 0;
54
    NSString *file;
55
    while (file = [directoryEnumerator nextObject]) {
56
        NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:[NSString stringWithFormat:@"%@/%@",directoryPath,file] error:nil];
57
        directorySize += [fileAttributes fileSize];
58
    }
59
    return directorySize;
60
}
61

  
62
+ (NSString *)humanReadableSize:(unsigned long long)bytes {
63
    if (bytes <= 999) {
64
        return [NSString stringWithFormat:@"%d B", (int)bytes];
65
    } else if (bytes <= 999000) {
66
        return [NSString stringWithFormat:@"%d KB", (int)ceil(bytes / 1000)];
67
    } else if (bytes <= 999900000) {
68
        double megabytes = floor(bytes / 1000000);
69
        double hundredkilobytes = ceil((bytes - megabytes * 1000000) / 100000);
70
        if (hundredkilobytes == 10) {
71
            megabytes++;
72
            hundredkilobytes = 0;
73
        }
74
        if (hundredkilobytes == 0) {
75
            return [NSString stringWithFormat:@"%d MB", (int)megabytes];
76
        } else {
77
            return [NSString stringWithFormat:@"%d.%d MB", (int)megabytes, (int)hundredkilobytes];
78
        }
79
    } else {
80
        double gigabytes = floor(bytes / 1000000000);
81
        double hundredmegabytes = ceil((bytes - gigabytes * 1000000000) / 100000000);
82
        if (hundredmegabytes == 10) {
83
            gigabytes++;
84
            hundredmegabytes = 0;
85
        }
86
        if (hundredmegabytes == 0) {
87
            return [NSString stringWithFormat:@"%d GB", (int)gigabytes];
88
        } else {
89
            return [NSString stringWithFormat:@"%d.%d GB", (int)gigabytes, (int)hundredmegabytes];
90
        }
91
    }
92
}
93

  
50 94
@end
b/Classes/SettingsViewController.h
9 9
#import <UIKit/UIKit.h>
10 10

  
11 11
@interface SettingsViewController : UITableViewController {
12
    NSInteger cacheSection;
12 13
    NSInteger aboutSection;
13 14
}
14 15

  
b/Classes/SettingsViewController.m
14 14
#import "SettingsPluginHandler.h"
15 15
#import "SettingsPlugin.h"
16 16
#import "AboutViewController.h"
17
#import "OpenStackAppDelegate.h"
18
#import "PithosUtilities.h"
17 19

  
18 20
#define kPasscodeLock 0
19 21

  
......
37 39
    self.navigationItem.title = @"Settings";
38 40
    
39 41
    //aboutSection = 1 + [[SettingsPluginHandler plugins] count];
40
    aboutSection = 1;
42
    cacheSection = 1;
43
    aboutSection = 2;
41 44
    UIBarButtonItem *doneButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(doneButtonPressed:)];
42 45
    self.navigationItem.rightBarButtonItem = doneButton;
43 46
    [doneButton release];
......
47 50
#pragma mark Table view data source
48 51

  
49 52
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
50
    if (section > kPasscodeLock && section < aboutSection) {
53
    if (section > kPasscodeLock && section < cacheSection) {
51 54
        id <SettingsPlugin> plugin = [[SettingsPluginHandler plugins] objectAtIndex:section - 1];
52 55
        return [plugin tableView:tableView titleForFooterInSection:section];
53 56
    } else {
......
57 60

  
58 61
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
59 62
    //return 2 + [[SettingsPluginHandler plugins] count];
60
    return 2;
63
    return 3;
61 64
}
62 65

  
63 66

  
......
86 89
    if (cell == nil) {
87 90
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
88 91
    }
92
    cell.userInteractionEnabled = YES;
93
    cell.textLabel.textColor = [UIColor blackColor];
89 94
    
90 95
    // Configure the cell...
91 96
    if (indexPath.section == kPasscodeLock) {
......
110 115
        }
111 116
        
112 117
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
118
    } else if (indexPath.section == cacheSection) {
119
        OpenStackAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
120
        NSString *cacheSize = [PithosUtilities humanReadableSize:[PithosUtilities sizeOfDirectory:appDelegate.cacheDirectoryPath]];
121
        if ([cacheSize hasPrefix:@"0"]) {
122
            cell.textLabel.textColor = [UIColor grayColor];
123
            cell.userInteractionEnabled = NO;
124
        }
125
        cell.textLabel.text = [NSString stringWithFormat:@"Clear Cache  (%@)", cacheSize];
126
        cell.detailTextLabel.text = @"";
127
        cell.accessoryType = UITableViewCellAccessoryNone;
113 128
    } else if (indexPath.section == aboutSection) {
114 129
        cell.textLabel.text = @"About This App";
115 130
        cell.detailTextLabel.text = @"";
......
138 153
        vc.settingsViewController = self;
139 154
        [self.navigationController pushViewController:vc animated:YES];
140 155
        [vc release];
156
    } else if (indexPath.section == cacheSection) {
157
        OpenStackAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
158
        NSFileManager *fileManager = [NSFileManager defaultManager];
159
        NSError *error = nil;
160
        NSString *file;
161
        NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtPath:appDelegate.cacheDirectoryPath];
162
        @synchronized(appDelegate.cachedObjectsDictionary) {
163
            while (file = [directoryEnumerator nextObject]) {
164
                [fileManager removeItemAtPath:[NSString stringWithFormat:@"%@/%@",appDelegate.cacheDirectoryPath,file] error:&error];
165
                if (error) {
166
                    [self alert:@"Error" message:[NSString stringWithFormat:@"Error in removing cached file, %@", error.localizedDescription]];
167
                } else {
168
                    for (NSString *key in [appDelegate.cachedObjectsDictionary allKeys])
169
                        if ([[appDelegate.cachedObjectsDictionary objectForKey:key] isEqualToString:file])
170
                            [appDelegate.cachedObjectsDictionary removeObjectForKey:key];
171
                }        
172
            }
173
        }
174
        [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
175
        [self.tableView reloadData];
141 176
    } else if (indexPath.section == aboutSection) {
142 177
        //This currently is a link to the pithos docs page. It might change in the future to use the AboutViewController;
143 178
        NSString *aboutURLString = [[NSUserDefaults standardUserDefaults] objectForKey:@"aboutURL"];
......
146 181
        /*AboutViewController *vc = [[AboutViewController alloc] initWithNibName:@"AboutViewController" bundle:nil];
147 182
        [self.navigationController pushViewController:vc animated:YES];
148 183
        [vc release];*/
149
    } else {
184
    }  else {
150 185
        id <SettingsPlugin> plugin = [[SettingsPluginHandler plugins] objectAtIndex:indexPath.section - 1];
151 186
        return [plugin tableView:tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath];
152 187
    }
b/Classes/SharingAccountsViewController.m
190 190
    [self.navigationController pushViewController:vc animated:YES];
191 191
    [vc refreshButtonPressed:nil];
192 192
    [vc release];
193
    [self.tableView deselectRowAtIndexPath:indexPath animated:NO];
193
    [self.tableView deselectRowAtIndexPath:indexPath animated:YES];
194 194
}
195 195

  
196 196

  
b/Classes/StorageObject.h
35 35

  
36 36
- (NSString *)humanizedBytes;
37 37
+ (StorageObject *)fromJSON:(NSDictionary *)dict;
38
- (void)setPropertiesfromResponseHeaders:(NSDictionary *)headers;
38 39

  
39 40
- (BOOL)isPlayableMedia;
40 41

  
b/Classes/StorageObject.m
58 58
    
59 59
    object.name = [dict objectForKey:@"name"]; // this may be shortened by folder parsing later
60 60
    object.fullPath = [dict objectForKey:@"name"];
61
    object.hash = [dict objectForKey:@"hash"];
61
    object.hash = [dict objectForKey:@"x_object_hash"];
62 62
    object.bytes = [[dict objectForKey:@"bytes"] intValue];
63 63
    object.contentType = [dict objectForKey:@"content_type"];
64 64
    object.lastModified = [ComputeModel dateFromString:[dict objectForKey:@"last_modified"]];
......
77 77
    return object;
78 78
}
79 79

  
80
- (void)setPropertiesfromResponseHeaders:(NSDictionary *)headers {
81
    self.hash = [headers objectForKey:@"X-Object-Hash"];
82
    self.bytes = [[headers objectForKey:@"Content-Length"] intValue];
83
    self.contentType = [headers objectForKey:@"Content-Type"];
84
    self.lastModified = [headers objectForKey:@"Last-Modified"];
85
    self.publicURI = [headers objectForKey:@"X-Object-Public"];
86
    self.sharing = [headers objectForKey:@"X-Object-Sharing"];
87
    
88
    self.metadata = [NSMutableDictionary dictionary];
89
    for (NSString *key in headers) {
90
        if ([key hasPrefix:@"X-Object-Meta-"]) {
91
            NSString *metadataValue = [headers objectForKey:key];
92
            NSString *metadataKey = [key substringFromIndex:14];
93
            [self.metadata setObject:metadataValue forKey:metadataKey];
94
        }
95
    }
96
}
97

  
80 98
#pragma mark -
81 99
#pragma mark Presentation
82 100

  
b/Classes/StorageObjectViewController.h
13 13
#import <MessageUI/MessageUI.h>
14 14
#import <MessageUI/MFMailComposeViewController.h>
15 15
#import "ActivityIndicatorView.h"
16
#import "OpenStackAppDelegate.h"
16 17

  
17 18
@class OpenStackAccount, Container, Folder, StorageObject, AnimatedProgressView, FolderViewController;
18 19

  
......
41 42
    NSInteger cdnURLSection;
42 43
    NSInteger actionsSection;
43 44
    NSInteger deleteSection;
45
    NSInteger versionsSection;
46
    NSInteger publicLinkSection;
47
    NSInteger permissionsSection;
44 48
    
45 49
    NSString *oldObjectSharingString;
46 50
    NSString *oldPublicURI;
47 51
    NSMutableDictionary *permissions;
52
    NSIndexPath *actionSelectedIndexPath;
53
    NSString *versionID;
54
    OpenStackAppDelegate *appDelegate;
48 55
}
49 56

  
50 57
@property (nonatomic, retain) OpenStackAccount *account;
......
55 62
@property (nonatomic, retain) FolderViewController *folderViewController;
56 63
    @property (nonatomic, retain) NSString *oldPubicURI;
57 64
@property (nonatomic, retain) UIDocumentInteractionController *documentInteractionController;
65
@property (nonatomic, retain) NSIndexPath *actionSelectedIndexPath;
66
@property (nonatomic, assign) BOOL objectIsReadOnly;
67
@property (nonatomic, retain) NSString *versionID;
58 68

  
59 69
- (void)setProgress:(float)newProgress;
60 70
- (IBAction)homeButtonPressed:(id)sender;
b/Classes/StorageObjectViewController.m
25 25
#import "TextViewCell.h"
26 26
#import "NSString+Conveniences.h"
27 27
#import "APICallback.h"
28
#import "ObjectVersionsViewController.h"
28 29

  
29 30
#define kDetails 0
30 31
#define kMetadata 1
31
#define kPublic 2
32
#define kPermissions 3
33 32

  
34 33
#define maxMetadataViewableLength 12
35 34

  
......
59 58
@implementation StorageObjectViewController
60 59

  
61 60
@synthesize account, container, folder, object, tableView, folderViewController;
62
@synthesize oldPubicURI, documentInteractionController;
61
@synthesize oldPubicURI, documentInteractionController, actionSelectedIndexPath, objectIsReadOnly, versionID;
63 62

  
64 63
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation {
65 64
    return (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) || (toInterfaceOrientation == UIInterfaceOrientationPortrait);
......
120 119
        }
121 120
        objectIsPublicSwitch.enabled = NO;
122 121
    }
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];
123 132
}
124 133

  
125 134
- (void)viewWillAppear:(BOOL)animated {
......
129 138
    NSIndexPath *selection = [self.tableView indexPathForSelectedRow];
130 139
	if (selection)
131 140
		[self.tableView deselectRowAtIndexPath:selection animated:YES];
132
    
133
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
134
    NSString *documentsDirectory = [paths objectAtIndex:0];        
135
    NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
136
    NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
137
    
138
    NSFileManager *fileManager = [NSFileManager defaultManager];
139
    fileDownloaded = [fileManager fileExistsAtPath:filePath];
140
    
141
    downloadProgressView = [[AnimatedProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
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
    }
142 152
    
143 153
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
144 154
        CGRect rect = downloadProgressView.frame;
......
147 157
    }
148 158
    
149 159
    //[self setBackgroundView];
160
    publicLinkSection = 2;
161
    permissionsSection = 3;
150 162
    if (self.container.cdnEnabled) {
151 163
        cdnURLSection = 4;
152 164
        actionsSection = 5;
153 165
        deleteSection = 6;
166
        versionsSection = 7;
154 167
    } else {
155 168
        cdnURLSection = -1;
156 169
        actionsSection = 4;
157 170
        deleteSection = 5;
171
        versionsSection = 6;
158 172
    }
159 173
    
160
    if (account.sharingAccount || account.shared) {
174
    if (versionID) {
175
        publicLinkSection = -1;
176
        permissionsSection = -1;
177
        versionsSection = -1;
161 178
        deleteSection = -1;
179
        actionsSection = 2;
180
        objectIsReadOnly = YES;
162 181
    }
182
    if (account.sharingAccount || account.shared)
183
            deleteSection = -1;
184
    
163 185
    
164 186
    // let's see if we can tweet
165 187
    UIApplication *app = [UIApplication sharedApplication];
......
179 201
#pragma mark Table view data source
180 202

  
181 203
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
182
    if (account.sharingAccount || account.shared)
183
        return self.container.cdnEnabled ? 6 : 5;
184
    else
185
        return self.container.cdnEnabled ? 7 : 6;
204
    int numberOfSections = 8;
205
    if (!self.container.cdnEnabled)
206
        numberOfSections--;
207
    if (deleteSection < 0)
208
        numberOfSections--;
209
    if (versionsSection < 0)
210
        numberOfSections--;
211
    if (publicLinkSection < 0)
212
        numberOfSections--;
213
    if (permissionsSection < 0)
214
        numberOfSections--;
215

  
216
    return numberOfSections;
186 217
}
187 218

  
188 219
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
189 220
    if (section == kDetails) {
190 221
        return 4;
191 222
    } else if (section == kMetadata) {
192
        if (objectIsReadOnly)
223
        if (objectIsReadOnly) {
193 224
            return [object.metadata count];
194
        else
225
        } else {
195 226
            return 1 + [object.metadata count];
227
        }
196 228
    } else if (section == cdnURLSection) {
197 229
        return 1;
198 230
    } else if (section == actionsSection) {
199
        return fileDownloaded ? 2 : 1;
200
    } else if (section == kPublic) {
231
        return 2;
232
    } else if (section == publicLinkSection) {
201 233
        return objectIsPublic ? 2 : 1;
202
    } else if (section == kPermissions) {
203
        if (account.sharingAccount)
234
    } else if (section == permissionsSection) {
235
        if (account.sharingAccount || objectIsReadOnly)
204 236
            return [permissions count];
205 237
        else
206 238
            return 1 + [permissions count];
......
244 276
        }
245 277
        CGSize stringSize = [object.name sizeWithFont:[UIFont systemFontOfSize:18.0] constrainedToSize:textLabelSize lineBreakMode:UILineBreakModeWordWrap];
246 278
        return 22.0 + stringSize.height;
247
    } else if (indexPath.section == kPublic && indexPath.row == 1) {
279
    } else if (indexPath.section == publicLinkSection && indexPath.row == 1) {
248 280
        NSString *publicLinkUrl = [NSString stringWithFormat:@"%@%@",
249 281
                                   account.pithosPublicLinkURLPrefix,
250 282
                                   self.object.publicURI];
......
259 291
    static NSString *CellIdentifier = @"Cell";
260 292
    static NSString *TextViewCellIdentifier = @"TextViewCell";
261 293
    
262
    if (indexPath.section == kPublic) {
294
    if (indexPath.section == publicLinkSection) {
263 295
        if (indexPath.row == 1) {
264 296
            TextViewCell *cell = [aTableView dequeueReusableCellWithIdentifier:TextViewCellIdentifier];
265 297
            if (cell == nil) {
......
289 321
        cell.detailTextLabel.numberOfLines = 0;
290 322
        cell.detailTextLabel.lineBreakMode = UILineBreakModeWordWrap;
291 323
    }
292
    
324
    cell.textLabel.textColor = [UIColor blackColor];
293 325
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
294 326
        cell.backgroundColor = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8];
295 327
    }
......
348 380
            cell.textLabel.text = metadataKeyCellText;
349 381
            cell.detailTextLabel.text = metadataValueCellText;
350 382
        }
351
    } else if (indexPath.section == kPublic) {
383
    } else if (indexPath.section == publicLinkSection) {
352 384
        if (indexPath.row == 0) {
353 385
            cell.textLabel.text = @"Public URL";        
354 386
            cell.detailTextLabel.text = @"";
......
356 388
            cell.accessoryType = UITableViewCellAccessoryNone;
357 389
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
358 390
        }
359
    } else if (indexPath.section == kPermissions) {
391
    } else if (indexPath.section == permissionsSection) {
360 392
        if (account.sharingAccount) {
361 393
            cell.accessoryType = UITableViewCellAccessoryNone;
362 394
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
......
383 415
        cell.textLabel.text = @"";
384 416
        cell.detailTextLabel.text = [[NSString stringWithFormat:@"%@/%@", self.container.cdnURL, self.object.fullPath] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
385 417
    } else if (indexPath.section == actionsSection) {
386
        cell.accessoryView = nil;
387
        if (performingAction) {
388
            cell.textLabel.textColor = [UIColor grayColor];
389
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
390
            cell.accessoryType = UITableViewCellAccessoryNone;
418
        if (fileDownloading) {
419
            if (actionSelectedIndexPath.row == indexPath.row) {
420
                cell.accessoryView = downloadProgressView;
421
            } else {
422
                cell.textLabel.textColor = [UIColor grayColor];
423
                cell.selectionStyle = UITableViewCellSelectionStyleNone;
424
            }
391 425
        } else {
392
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
393
            cell.selectionStyle = UITableViewCellSelectionStyleNone;
426
            cell.selectionStyle = UITableViewCellSelectionStyleBlue;
427
            cell.accessoryView = nil;
394 428
        }
395

  
396 429
        if (indexPath.row == 0) {
397
            if (fileDownloaded) {
398
                cell.textLabel.text = @"Open File"; 
399
                cell.accessoryType = UITableViewCellAccessoryNone;
400
                cell.selectionStyle = UITableViewCellSelectionStyleBlue;
401
            } else {
402
                if (fileDownloading) {
403
                    cell.accessoryView = downloadProgressView;
404
                    // TODO: if you leave this view while downloading, there's EXC_BAD_ACCESS
405
                    cell.textLabel.text = @"Downloading";
406
                } else {
407
                    cell.textLabel.text = @"Download File";
408
                }
409
            }
430
            cell.textLabel.text = @"Open File"; 
431
            cell.accessoryType = UITableViewCellAccessoryNone;
410 432
            cell.detailTextLabel.text = @"";
411 433
            
412 434
        } else if (indexPath.row == 1) {
413 435
            cell.textLabel.text = @"Email File as Attachment";
414 436
            cell.detailTextLabel.text = @"";
415
            cell.selectionStyle = UITableViewCellSelectionStyleBlue;
416
        }
437
            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
438
        }        
417 439
    } else if (indexPath.section == deleteSection) {
418 440
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
419 441
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
420 442
        cell.textLabel.text = @"Delete Object";
421 443
        cell.detailTextLabel.text = @"";
422 444
        
445
    } else if (indexPath.section == versionsSection) {
446
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
447
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
448
        cell.textLabel.text = @"Versions";
449
        cell.detailTextLabel.text = @"";
423 450
    }
424 451
    
425 452
    return cell;
......
460 487
        vc.object = object;
461 488
        [self.navigationController pushViewController:vc animated:YES];
462 489
        [vc release];
463
    } else if (indexPath.section == kPermissions) {
490
    } else if (indexPath.section == permissionsSection) {
464 491
        EditPermissionsViewController *vc = [[EditPermissionsViewController alloc] initWithNibName:@"EditPermissionsViewController" bundle:nil];
465 492
        NSString *user;
466 493
        
......
497 524
    } else if (indexPath.section == cdnURLSection) {
498 525
        [cdnURLActionSheet showInView:self.view];
499 526
    } else if (indexPath.section == actionsSection) {
500
        if (indexPath.row == 0) {
527
        if (!fileDownloaded) {
528
            if (!fileDownloading) {
529
                // download the file
530
                fileDownloading = YES;
531
                self.actionSelectedIndexPath = indexPath;
532
                [self.tableView reloadData];
533
                NSMutableDictionary *requestUserInfo = [NSDictionary dictionaryWithObject:indexPath forKey:@"actionSelectedIndexPath"];
534
                [[self.account.manager getObject:self.container object:self.object downloadProgressDelegate:self requestUserInfo:requestUserInfo version:versionID]
535
                 success:^(OpenStackRequest *request) {
536
                     fileDownloaded = YES;
537
                     fileDownloading = NO;
538
                     if ([self.navigationController.visibleViewController isEqual:self])
539
                         [self.tableView.delegate tableView:self.tableView didSelectRowAtIndexPath:indexPath]; 
540
                     [self.tableView reloadData];
541
                     [self.folderViewController.tableView reloadData];
542
                 }
543
                 failure:^(OpenStackRequest *request) {
544
                     fileDownloaded = NO;
545
                     fileDownloading = NO;
546
                     [self alert:@"File failed to download" request:request];
547
                     [self.tableView reloadData];
548
                 }];
549
                [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexPath.row inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
550
            }
551
        } else if (indexPath.row == 0) {        
501 552
            if (fileDownloaded) {
502
                NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
503
                NSString *documentsDirectory = [paths objectAtIndex:0];        
504
                NSString *shortPath = [NSString stringWithFormat:@"/%@/%@", self.container.name, self.object.fullPath];
505
                NSString *filePath = [documentsDirectory stringByAppendingString:shortPath];
506
                                
553
                NSString *filePath = [appDelegate.cachedObjectsDictionary objectForKey:object.hash];                                 
507 554
                self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:filePath]];
508 555
                self.documentInteractionController.delegate = self;
556
                self.documentInteractionController.name = object.name;
509 557

  
510 558
                UITableViewCell *openFileCell = [self.tableView cellForRowAtIndexPath:indexPath];
511 559
                if (![self.documentInteractionController presentOptionsMenuFromRect:openFileCell.frame inView:self.view animated:YES]) {
......
523 571
                
524 572
                [self.tableView deselectRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:actionsSection] animated:YES];                
525 573
                
526
            } else {                
527
                if (!fileDownloading) {
528
                    // download the file
529
                    fileDownloading = YES;
530
                    [self.account.manager getObject:self.container object:self.object downloadProgressDelegate:self];
531
                    [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
532
                }
533
                
534
            }
574
            } 
535 575
        } else if (indexPath.row == 1) {
536 576
            
537 577
            NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
......
566 606
        }
567 607
    } else if (indexPath.section == deleteSection) {
568 608
        [deleteActionSheet showInView:self.view];
609
    } else if (indexPath.section == versionsSection) {
610
        ObjectVersionsViewController *vc = [[ObjectVersionsViewController alloc] initWithNibName:@"ObjectVersionsViewController" bundle:nil];
611
        vc.account = account;
612
        vc.container = container;
613
        vc.object = object;
614
        [self.navigationController pushViewController:vc animated:YES];
615
        [vc release];
569 616
    }
570 617
}
571 618

  
......
574 621
    if (newProgress >= 1.0) {
575 622
        fileDownloading = NO;
576 623
        fileDownloaded = YES;
577
        
578
        [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:1 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationBottom];
579
        [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:0 inSection:actionsSection]] withRowAnimation:UITableViewRowAnimationNone];
624
        [self.tableView reloadData];
580 625
    }
581 626
}
582 627

  
......
713 758
    objectIsPublic = !objectIsPublic;
714 759
    [[self.account.manager writeObjectMetadata:container object:object] 
715 760
     success:^(OpenStackRequest *request) {
716
         NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:kPublic];
761
         NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:publicLinkSection];
717 762
         if (objectIsPublic) {
718
             [[self.account.manager getObjectInfo:container object:object] 
763
             [[self.account.manager getObjectInfo:container object:object version:versionID] 
719 764
              success:^(OpenStackRequest *request) {
720 765
                  [activityIndicatorView removeFromSuperviewAndRelease];
721 766
                  object.publicURI = [request.responseHeaders objectForKey:@"X-Object-Public"];
722
                  NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:kPublic];
767
                  NSIndexPath *publicURICellIndexPath = [NSIndexPath indexPathForRow:1 inSection:publicLinkSection];
723 768
                  [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:publicURICellIndexPath]
724 769
                                        withRowAnimation:UITableViewRowAnimationBottom];
725 770
              }
......
750 795
#pragma mark Memory management
751 796

  
752 797
- (void)dealloc {
798
    if (fileDownloading) {
799
        OpenStackRequest *request = [self.account.manager.objectDownloadRequests objectForKey:object.fullPath];
800
        [request setDownloadProgressDelegate:nil];
801
    }
753 802
    [account release];
754 803
    [downloadProgressView release];
755 804
    [deleteActionSheet release];
......
759 808
    [objectIsPublicSwitch release];
760 809
    [permissions release];
761 810
    [documentInteractionController release];
811
    [actionSelectedIndexPath release];
812
    [versionID release];
762 813

  
763 814
    [super dealloc];
764 815
}
b/OpenStack.xcodeproj/project.pbxproj
28 28
		248547AC144C4B8200E48921 /* AccountGroupsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 248547AA144C4B8200E48921 /* AccountGroupsViewController.xib */; };
29 29
		248547BB144C614D00E48921 /* EditAccountGroupsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 248547B9144C613700E48921 /* EditAccountGroupsViewController.m */; };
30 30
		248547BC144C614D00E48921 /* EditAccountGroupsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 248547BA144C613A00E48921 /* EditAccountGroupsViewController.xib */; };
31
		249F004C14BC836A000AB729 /* ObjectVersionsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 249F004A14BC8369000AB729 /* ObjectVersionsViewController.m */; };
32
		249F004D14BC836A000AB729 /* ObjectVersionsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 249F004B14BC8369000AB729 /* ObjectVersionsViewController.xib */; };
31 33
		24A18E22143DB870003232F1 /* EditMetadataViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A18E20143DB870003232F1 /* EditMetadataViewController.m */; };
32 34
		24A18E23143DB870003232F1 /* EditMetadataViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 24A18E21143DB870003232F1 /* EditMetadataViewController.xib */; };
33 35
		24A18E56143F0429003232F1 /* FolderDetailViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 24A18E54143F0428003232F1 /* FolderDetailViewController.m */; };
......
1116 1118
		248547B8144C613400E48921 /* EditAccountGroupsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditAccountGroupsViewController.h; sourceTree = "<group>"; };
1117 1119
		248547B9144C613700E48921 /* EditAccountGroupsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EditAccountGroupsViewController.m; sourceTree = "<group>"; };
1118 1120
		248547BA144C613A00E48921 /* EditAccountGroupsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EditAccountGroupsViewController.xib; sourceTree = "<group>"; };
1121
		249F004914BC8369000AB729 /* ObjectVersionsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ObjectVersionsViewController.h; sourceTree = "<group>"; };
1122
		249F004A14BC8369000AB729 /* ObjectVersionsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ObjectVersionsViewController.m; sourceTree = "<group>"; };
1123
		249F004B14BC8369000AB729 /* ObjectVersionsViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ObjectVersionsViewController.xib; sourceTree = "<group>"; };
1119 1124
		24A18E1F143DB86F003232F1 /* EditMetadataViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditMetadataViewController.h; sourceTree = "<group>"; };
1120 1125
		24A18E20143DB870003232F1 /* EditMetadataViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EditMetadataViewController.m; sourceTree = "<group>"; };
1121 1126
		24A18E21143DB870003232F1 /* EditMetadataViewController.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = EditMetadataViewController.xib; sourceTree = "<group>"; };
......
3199 3204
				278906DB12BEDAB5007112B6 /* StorageObjectViewController.h */,
3200 3205
				278907A112BEF72C007112B6 /* StorageObjectViewController.m */,
3201 3206
				278906DD12BEDAB5007112B6 /* StorageObjectViewController.xib */,
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff