Minor UI changes.
[pithos-macos] / pithos-macos / PithosContainerNode.m
1 //
2 //  PithosContainerNode.m
3 //  pithos-macos
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 "PithosContainerNode.h"
39 #import "PithosObjectNode.h"
40 #import "PithosSubdirNode.h"
41 #import "ASIPithosContainerRequest.h"
42 #import "ASIPithosContainer.h"
43 #import "ASIPithosObject.h"
44 #import "ASIDownloadCache.h"
45 #import "PithosFileUtilities.h"
46 #import "PithosContainerNodeInfoController.h"
47
48 static NSImage *sharedIcon = nil;
49
50 @implementation PithosContainerNode
51 @synthesize pithosContainer, prefix;
52 @synthesize policyVersioning, policyQuota;
53
54 + (void)initialize {
55         if (self == [PithosContainerNode class])
56         sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain];
57 }
58
59 #pragma mark -
60 #pragma mark Object Lifecycle
61
62 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
63     if ((self = [super init])) {
64         self.pithosContainer = aPithosContainer;
65         prefix = nil;
66         icon = [anIcon retain];
67         childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated";
68     }
69     return self;
70 }
71
72 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer {
73     return [self initWithPithosContainer:aPithosContainer icon:nil];
74 }
75
76 - (id)initWithContainerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
77     ASIPithosContainer *container = [ASIPithosContainer container];
78     container.name = aContainerName;
79     return [self initWithPithosContainer:container icon:anIcon];
80 }
81
82 - (id)initWithContainerName:(NSString *)aContainerName {
83     return [self initWithContainerName:aContainerName icon:nil];
84 }
85
86 - (void)dealloc {
87     [containerRequest clearDelegatesAndCancel];
88     [containerRequest release];
89     [refreshMetadataContainerRequest clearDelegatesAndCancel];
90     [refreshMetadataContainerRequest release];
91     [applyMetadataContainerRequest clearDelegatesAndCancel];
92     [applyMetadataContainerRequest release];
93     [policyQuota release];
94     [policyVersioning release];
95     [childrenUpdatedNotificationName release];
96     [prefix release];
97     [objects release];
98     [pithosContainer release];
99     [super dealloc];
100 }
101
102 #pragma mark -
103 #pragma mark ASIHTTPRequestDelegate
104
105 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
106     NSLog(@"URL: %@", [containerRequest url]);
107     NSLog(@"cached: %d", [containerRequest didUseCachedResponse]);
108     
109     if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
110         pithosContainer.blockHash = [containerRequest blockHash];
111         pithosContainer.blockSize = [containerRequest blockSize];
112     }
113
114     NSArray *someObjects = [containerRequest objects];
115     if (objects == nil) {
116         objects = [[NSMutableArray alloc] initWithArray:someObjects];
117     } else {
118         [objects addObjectsFromArray:someObjects];
119     }
120     if ([someObjects count] < 10000) {
121         if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
122             // Save new children
123             NSLog(@"using newChildren");
124             newChildren = [[NSMutableArray alloc] init];
125             NSArray *objectNames = [objects valueForKey:@"name"];
126             NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
127             BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
128             for (ASIPithosObject *object in objects) {
129                 if (!isSubdirNode || 
130                     [object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]]) {
131                     // The check above removes false objects due to trailing slash or same prefix
132                     if (object.subdir) {
133                         NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
134                         if ((sameNameObjectIndex == NSNotFound) ||
135                             ![[[objects objectAtIndex:sameNameObjectIndex] contentType] isEqualToString:@"application/directory"]) {
136                             PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
137                             node.parent = self;
138                             if (children) {
139                                 NSUInteger oldIndex = [children indexOfObject:node];
140                                 if (oldIndex != NSNotFound) {
141                                     // Use the same pointer value, if possible
142                                     node = [children objectAtIndex:oldIndex];
143                                     node.pithosContainer = pithosContainer;
144                                     node.pithosObject = object;
145                                     [keptNodes addIndex:oldIndex];
146                                 }
147                             }
148                             [newChildren addObject:node];
149                         }
150                     } else if ([object.contentType isEqualToString:@"application/directory"]) {
151                         PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
152                         node.parent = self;
153                         if (children) {
154                             NSUInteger oldIndex = [children indexOfObject:node];
155                             if (oldIndex != NSNotFound) {
156                                 // Use the same pointer value, if possible
157                                 node = [children objectAtIndex:oldIndex];
158                                 node.pithosContainer = pithosContainer;
159                                 node.pithosObject = object;
160                                 [keptNodes addIndex:oldIndex];
161                             }
162                         }
163                         [newChildren addObject:node];
164                     } else {
165                         PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
166                         node.parent = self;
167                         if (children) {
168                             NSUInteger oldIndex = [children indexOfObject:node];
169                             if (oldIndex != NSNotFound) {
170                                 // Use the same pointer value, if possible
171                                 node = [children objectAtIndex:oldIndex];
172                                 node.pithosContainer = pithosContainer;
173                                 node.pithosObject = object;
174                                 [keptNodes addIndex:oldIndex];
175                             }
176                         }
177                         [newChildren addObject:node];                                
178                     }
179                 }
180             }
181             [[children objectsAtIndexes:
182               [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
183                 if ([keptNodes containsIndex:idx])
184                     return NO;
185                 return YES;
186             }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
187         }
188         // Else cache was used and all results were fetched during this request, so existing children can be reused
189         [containerRequest release];
190         containerRequest = nil;
191         [objects release];
192         objects = nil;
193         @synchronized(self) {
194             freshness = PithosNodeStateRefreshFinished;
195         }
196         // Notify observers that children are updated
197         [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
198     } else {
199         [containerRequest release];
200         // Do an additional request to fetch more objects
201         containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name 
202                                                                                      limit:0 
203                                                                                     marker:[[someObjects lastObject] name] 
204                                                                                     prefix:prefix 
205                                                                                  delimiter:@"/" 
206                                                                                       path:nil 
207                                                                                       meta:nil 
208                                                                                     shared:NO 
209                                                                                      until:nil] retain];
210         containerRequest.delegate = self;
211         containerRequest.downloadCache = [ASIDownloadCache sharedCache];
212         [containerRequest startAsynchronous];
213     }
214 }
215
216 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
217     [PithosFileUtilities httpRequestErrorAlertWithRequest:request];
218     [newChildren release];
219     newChildren = nil;
220     [containerRequest release];
221     containerRequest = nil;
222     [objects release];
223     objects = nil;
224     @synchronized(self) {
225         freshness = PithosNodeStateRefreshNeeded;
226     }
227 }
228
229 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
230     NSLog(@"URL: %@", [request url]);
231     NSLog(@"cached: %d", [request didUseCachedResponse]);
232     
233     if ([request isEqualTo:applyMetadataContainerRequest]) {
234         @synchronized(self) {
235             [applyMetadataContainerRequest release];
236             applyMetadataContainerRequest = nil;
237         }
238         [self refreshInfo];
239     } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
240         [[pithosNodeInfoController window] makeFirstResponder:nil];
241         self.pithosContainer = [refreshMetadataContainerRequest container];
242         @synchronized(self) {
243             [refreshMetadataContainerRequest release];
244             refreshMetadataContainerRequest = nil;
245         }
246     }
247 }
248
249 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
250     if ([request isEqualTo:applyMetadataContainerRequest]) {
251         [PithosFileUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
252         @synchronized(self) {
253             [applyMetadataContainerRequest release];
254             applyMetadataContainerRequest = nil;
255         }
256     } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
257         [PithosFileUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
258         @synchronized(self) {
259             [refreshMetadataContainerRequest release];
260             refreshMetadataContainerRequest = nil;
261         }
262     }
263 }
264
265 #pragma mark -
266 #pragma mark Properties
267
268 - (NSString *)url {
269     if (url == nil) 
270         url = [[NSString alloc] initWithFormat:@"%@/%@", [ASIPithosRequest storageURL], pithosContainer.name];
271     return url;
272 }
273
274 - (NSArray *)children {
275     @synchronized(self) {
276         switch (freshness) {
277             case PithosNodeStateFresh:
278                 break;
279             case PithosNodeStateRefreshNeeded:
280                 freshness = PithosNodeStateRefreshing;
281                 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name 
282                                                                                              limit:0 
283                                                                                             marker:nil 
284                                                                                             prefix:prefix 
285                                                                                          delimiter:@"/" 
286                                                                                               path:nil 
287                                                                                               meta:nil 
288                                                                                             shared:NO 
289                                                                                              until:nil] retain];
290                 containerRequest.delegate = self;
291                 containerRequest.didFinishSelector = @selector(containerRequestFinished:);
292                 containerRequest.didFailSelector = @selector(containerRequestFailed:);
293                 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
294                 [containerRequest startAsynchronous];
295                 break;
296             case PithosNodeStateRefreshing:
297                 break;
298             case PithosNodeStateRefreshFinished:
299                 if (newChildren) {
300                     [children release];
301                     children = newChildren;
302                     newChildren = nil;
303                 }
304                 freshness = PithosNodeStateFresh;
305             default:
306                 break;
307         }
308         return children;
309     }
310 }
311
312 - (NSString *)displayName {
313     return [[pithosContainer.name copy] autorelease];
314 }
315
316 - (NSImage *)icon {
317     if (icon)
318         return icon;
319     return sharedIcon;
320 }
321
322 - (void)setIcon:(NSImage *)anIcon {
323     [icon release];
324     icon = [anIcon retain];
325 }
326
327 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
328     if (![pithosContainer isEqualTo:aPithosContainer]) {
329         [pithosContainer release];
330         pithosContainer = [aPithosContainer retain];
331     }
332     if (pithosContainer.policy) {
333         self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
334         self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
335     } else {
336         self.policyVersioning = @"manual";
337         self.policyQuota = [NSNumber numberWithLongLong:0];
338     }
339 }
340
341 #pragma mark -
342 #pragma mark Info
343
344 - (void)applyInfo {
345     @synchronized(self) {
346         if (applyMetadataContainerRequest == nil) {
347             [[pithosNodeInfoController window] makeFirstResponder:nil];
348             applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithContainerName:pithosContainer.name 
349                                                                                                                  policy:[NSDictionary dictionaryWithObjectsAndKeys:
350                                                                                                                          policyVersioning, @"versioning", 
351                                                                                                                          [policyQuota stringValue], @"quota", 
352                                                                                                                          nil] 
353                                                                                                                metadata:pithosContainer.metadata 
354                                                                                                                  update:NO] retain];
355             applyMetadataContainerRequest.delegate = self;
356             applyMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:);
357             applyMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:);
358             [applyMetadataContainerRequest startAsynchronous];
359         }
360     }
361 }
362
363 - (void)refreshInfo {
364     @synchronized(self) {
365         if (refreshMetadataContainerRequest == nil) {
366             refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithContainerName:pithosContainer.name] retain];
367             refreshMetadataContainerRequest.delegate = self;
368             refreshMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:);
369             refreshMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:);
370             refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
371             [refreshMetadataContainerRequest startAsynchronous];
372         }
373     }
374 }
375
376 #pragma mark -
377 #pragma mark Actions
378
379 - (void)showPithosNodeInfo:(id)sender {
380     if (!pithosNodeInfoController)
381         pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
382     [pithosNodeInfoController showWindow:sender];
383     [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
384     [NSApp activateIgnoringOtherApps:YES];
385 }
386
387 @end