Use user catalog displayname when displaying an account node
[pithos-macos] / pithos-macos / PithosContainerNode.m
1 //
2 //  PithosContainerNode.m
3 //  pithos-macos
4 //
5 // Copyright 2011-2012 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 "ASIPithos.h"
42 #import "ASIPithosContainerRequest.h"
43 #import "ASIPithosContainer.h"
44 #import "ASIPithosObject.h"
45 #import "ASIDownloadCache.h"
46 #import "PithosUtilities.h"
47 #import "PithosContainerNodeInfoController.h"
48 #import "PithosActivityFacility.h"
49
50 static NSImage *sharedIcon = nil;
51
52 @implementation PithosContainerNode
53 @synthesize pithos, pithosContainer, prefix;
54 @synthesize policyVersioning, policyQuota;
55
56 + (void)initialize {
57         if (self == [PithosContainerNode class])
58         sharedIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)];
59 }
60
61 #pragma mark -
62 #pragma mark Object Lifecycle
63
64 - (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
65     if ((self = [super init])) {
66         self.pithos = aPithos;
67         self.pithosContainer = aPithosContainer;
68         prefix = nil;
69         self.icon = anIcon;
70     }
71     return self;
72 }
73
74 - (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer {
75     return [self initWithPithos:aPithos pithosContainer:aPithosContainer icon:nil];
76 }
77
78 - (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
79     ASIPithosContainer *container = [ASIPithosContainer container];
80     container.name = aContainerName;
81     return [self initWithPithos:aPithos pithosContainer:container icon:anIcon];
82 }
83
84 - (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName {
85     return [self initWithPithos:aPithos containerName:aContainerName icon:nil];
86 }
87
88 - (void)dealloc {
89     [containerRequest clearDelegatesAndCancel];
90     [refreshMetadataContainerRequest clearDelegatesAndCancel];
91     [applyMetadataContainerRequest clearDelegatesAndCancel];
92 }
93
94 #pragma mark -
95 #pragma mark Properties
96
97 - (void)setPithos:(ASIPithos *)aPithos {
98     if (aPithos && ![aPithos isEqualTo:pithos]) {
99         pithos = aPithos;
100         url = nil;
101     }
102 }
103
104 - (NSString *)url {
105     if (url == nil)
106         url = [[NSString alloc] initWithFormat:@"%@/%@%@", 
107                (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
108                pithosContainer.name, 
109                (shared ? @"?shared" : @"")];
110     return url;
111 }
112
113 - (NSArray *)children {
114     @synchronized(self) {
115         switch (freshness) {
116             case PithosNodeStateFresh:
117                 break;
118             case PithosNodeStateRefreshNeeded:
119                 freshness = PithosNodeStateRefreshing;
120                 containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
121                                                                               containerName:pithosContainer.name 
122                                                                                       limit:0 
123                                                                                      marker:nil 
124                                                                                      prefix:prefix 
125                                                                                   delimiter:@"/" 
126                                                                                        path:nil 
127                                                                                        meta:nil 
128                                                                                      shared:shared 
129                                                                                       until:nil];
130                 if (sharingAccount)
131                     [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
132                 else if (!forcedRefresh)
133                     containerRequest.downloadCache = [ASIDownloadCache sharedCache];
134                 containerRequest.delegate = self;
135                 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
136                 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
137                 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
138                                              [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
139                                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
140                                              NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", 
141                                              NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", 
142                                              nil];
143                 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
144                 break;
145             case PithosNodeStateRefreshing:
146                 break;
147             case PithosNodeStateRefreshFinished:
148                 if (newChildren) {
149                     children = newChildren;
150                     newChildren = nil;
151                 }
152                 freshness = PithosNodeStateFresh;
153             default:
154                 break;
155         }
156         return children;
157     }
158 }
159
160 - (NSString *)displayName {
161     return [pithosContainer.name copy];
162 }
163
164 - (void)setDisplayName:(NSString *)aDisplayName {
165 }
166
167 - (NSImage *)icon {
168     if (icon == nil) {
169         if ([pithosContainer.name isEqualToString:@"pithos"])
170             icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
171         else if ([pithosContainer.name isEqualToString:@"trash"])
172             icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
173         else
174             icon = sharedIcon;
175     }
176     return icon;
177 }
178
179 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
180     if (![pithosContainer isEqualTo:aPithosContainer]) {
181         pithosContainer = aPithosContainer;
182     }
183     if (pithosContainer.policy) {
184         self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
185         self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
186     } else {
187         self.policyVersioning = @"manual";
188         self.policyQuota = [NSNumber numberWithLongLong:0];
189     }
190 }
191
192 - (void)setLimitedPithosContainer:(ASIPithosContainer *)aPithosContainer {
193     if (![pithosContainer isEqualTo:aPithosContainer]) {
194         self.pithosContainer.name = aPithosContainer.name;
195         self.pithosContainer.count = aPithosContainer.count;
196         self.pithosContainer.bytes = aPithosContainer.bytes;
197         self.pithosContainer.lastModified = aPithosContainer.lastModified;
198         self.pithosContainer.untilTimestamp = aPithosContainer.untilTimestamp;
199         if (!pithosNodeInfoController) {
200             self.pithosContainer.policy = aPithosContainer.policy;
201             self.pithosContainer = pithosContainer;
202         }
203     }
204 }
205
206 #pragma mark -
207 #pragma mark ASIHTTPRequestDelegate
208
209 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
210     @autoreleasepool {
211         NSString *message;
212         NSError *error = [containerRequest error];
213         if (error)
214             message = [NSString stringWithFormat:@"Container listing %@ failed: %@", containerRequest.url, [error localizedDescription]];
215         else
216             message = [NSString stringWithFormat:@"Container listing %@ failed: (%d) %@", 
217                        containerRequest.url, containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
218         dispatch_async(dispatch_get_main_queue(), ^{
219             [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
220         });
221         NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
222         if (retries > 0) {
223             ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
224             [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
225             containerRequest = newContainerRequest;
226             [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
227         } else {
228             newChildren = nil;
229             containerRequest = nil;
230             objects = nil;
231             forcedRefresh = NO;
232             @synchronized(self) {
233                 freshness = PithosNodeStateRefreshNeeded;
234             }
235         }
236     }
237 }
238
239 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
240     @autoreleasepool {
241         DLog(@"List container finished: %@", [containerRequest url]);
242         DLog(@"Cached: %d", [containerRequest didUseCachedResponse]);
243         if (containerRequest.responseStatusCode == 200) {
244             if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
245                 pithosContainer.blockHash = [containerRequest blockHash];
246                 pithosContainer.blockSize = [containerRequest blockSize];
247             }
248         
249             NSArray *someObjects = [containerRequest objects];
250             if (objects == nil) {
251                 objects = [[NSMutableArray alloc] initWithArray:someObjects];
252             } else {
253                 [objects addObjectsFromArray:someObjects];
254             }
255             if ([someObjects count] < 10000) {
256                 if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
257                     // Save new children
258                     DLog(@"using newChildren");
259                     newChildren = [[NSMutableArray alloc] init];
260                     NSArray *objectNames = [objects valueForKey:@"name"];
261                     NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
262                     BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
263                     for (ASIPithosObject *object in objects) {
264                         if (!isSubdirNode || 
265                             ([object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]] &&
266                              ([object.name length] > [((PithosSubdirNode *)self).prefix length] + 1))) {
267                             // The check above removes false objects due to trailing slash or same prefix
268                             if (object.subdir) {
269                                 NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
270                                 if ((sameNameObjectIndex == NSNotFound) || 
271                                     ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) {
272                                     PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithos:pithos 
273                                                                                        pithosContainer:pithosContainer 
274                                                                                           pithosObject:object];
275                                     node.parent = self;
276                                     node.shared = shared;
277                                     node.sharingAccount = sharingAccount;
278                                     node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
279                                     node.pithosAccountManager = pithosAccountManager;
280                                     if (children) {
281                                         NSUInteger oldIndex = [children indexOfObject:node];
282                                         if (oldIndex != NSNotFound) {
283                                             // Use the same pointer value, if possible
284                                             node = [children objectAtIndex:oldIndex];
285                                             node.pithosContainer = pithosContainer;
286 //                                          node.pithosObject = object;
287                                             [node setLimitedPithosObject:object];
288                                             [keptNodes addIndex:oldIndex];
289                                         }
290                                     }
291                                     if (sharingAccount)
292                                         node.pithosObject.allowedTo = @"read";
293                                     [newChildren addObject:node];
294                                 }
295                             } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
296                                 PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithos:pithos 
297                                                                                    pithosContainer:pithosContainer 
298                                                                                       pithosObject:object];
299                                 node.parent = self;
300                                 node.shared = shared;
301                                 node.sharingAccount = sharingAccount;
302                                 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
303                                 node.pithosAccountManager = pithosAccountManager;
304                                 if (children) {
305                                     NSUInteger oldIndex = [children indexOfObject:node];
306                                     if (oldIndex != NSNotFound) {
307                                         // Use the same pointer value, if possible
308                                         node = [children objectAtIndex:oldIndex];
309                                         node.pithosContainer = pithosContainer;
310 //                                      node.pithosObject = object;
311                                         [node setLimitedPithosObject:object];
312                                         [keptNodes addIndex:oldIndex];
313                                     }
314                                 }
315                                 [newChildren addObject:node];
316                             } else {
317                                 PithosObjectNode *node = [[PithosObjectNode alloc] initWithPithos:pithos 
318                                                                                    pithosContainer:pithosContainer 
319                                                                                       pithosObject:object];
320                                 node.parent = self;
321                                 node.shared = shared;
322                                 node.sharingAccount = sharingAccount;
323                                 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
324                                 node.pithosAccountManager = pithosAccountManager;
325                                 if (children) {
326                                     NSUInteger oldIndex = [children indexOfObject:node];
327                                     if (oldIndex != NSNotFound) {
328                                         // Use the same pointer value, if possible
329                                         node = [children objectAtIndex:oldIndex];
330                                         node.pithosContainer = pithosContainer;
331 //                                      node.pithosObject = object;
332                                         [node setLimitedPithosObject:object];
333                                         [keptNodes addIndex:oldIndex];
334                                     }
335                                 }
336                                 [newChildren addObject:node];                                
337                             }
338                         }
339                     }
340                     [[children objectsAtIndexes:
341                       [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
342                         if ([keptNodes containsIndex:idx])
343                             return NO;
344                         return YES;
345                     }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
346                 }
347                 // Else cache was used and all results were fetched during this request, so existing children can be reused
348                 containerRequest = nil;
349                 objects = nil;
350                 forcedRefresh = NO;
351                 @synchronized(self) {
352                     freshness = PithosNodeStateRefreshFinished;
353                 }
354                 [self postChildrenUpdatedNotificationName];
355             } else {
356                 // Do an additional request to fetch more objects
357                 containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos 
358                                                                               containerName:pithosContainer.name 
359                                                                                       limit:0 
360                                                                                      marker:[[someObjects lastObject] name] 
361                                                                                      prefix:prefix 
362                                                                                   delimiter:@"/" 
363                                                                                        path:nil 
364                                                                                        meta:nil 
365                                                                                      shared:shared 
366                                                                                       until:nil];
367                 if (sharingAccount)
368                     [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
369                 else if (!forcedRefresh)
370                     containerRequest.downloadCache = [ASIDownloadCache sharedCache];
371                 containerRequest.delegate = self;
372                 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
373                 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
374                 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
375                                              [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
376                                              [NSNumber numberWithUnsignedInteger:10], @"retries", 
377                                              NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector", 
378                                              NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector", 
379                                              nil];
380                 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
381             }
382         } else if (containerRequest.responseStatusCode == 304) {
383             // Container is not modified, so existing children can be reused
384             containerRequest = nil;
385             objects = nil;
386             forcedRefresh = NO;
387             @synchronized(self) {
388                 freshness = PithosNodeStateRefreshFinished;
389             }
390             [self postChildrenUpdatedNotificationName];
391         } else {
392             [self containerRequestFailed:containerRequest];
393         }
394     }
395 }
396
397 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
398     @autoreleasepool {
399         DLog(@"URL: %@", [request url]);
400         DLog(@"cached: %d", [request didUseCachedResponse]);
401         
402         if ([request isEqualTo:applyMetadataContainerRequest]) {
403             @synchronized(self) {
404                 applyMetadataContainerRequest = nil;
405             }
406             [self refreshInfo];
407         } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
408             [[pithosNodeInfoController window] makeFirstResponder:nil];
409             self.pithosContainer = [refreshMetadataContainerRequest container];
410             @synchronized(self) {
411                 refreshMetadataContainerRequest = nil;
412             }
413         }
414     }
415 }
416
417 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
418     @autoreleasepool {
419         NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
420         if (retries > 0) {
421             ASIPithosContainerRequest *newRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:request];
422             [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
423             if ([request isEqualTo:applyMetadataContainerRequest]) {
424                 @synchronized(self) {
425                     applyMetadataContainerRequest = newRequest;
426                 }
427             } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
428                 @synchronized(self) {
429                     refreshMetadataContainerRequest = newRequest;
430                 }
431             }
432             [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
433         } else {
434             if ([request isEqualTo:applyMetadataContainerRequest]) {
435                 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
436                 @synchronized(self) {
437                     applyMetadataContainerRequest = nil;
438                 }
439             } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
440                 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
441                 @synchronized(self) {
442                     refreshMetadataContainerRequest = nil;
443                 }
444             }
445         }
446     }
447 }
448
449 #pragma mark -
450 #pragma mark Info
451
452 - (void)applyInfo {
453     @synchronized(self) {
454         if (applyMetadataContainerRequest == nil) {
455             [[pithosNodeInfoController window] makeFirstResponder:nil];
456             applyMetadataContainerRequest = [ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithos 
457                                                                                                    containerName:pithosContainer.name 
458                                                                                                           policy:[NSDictionary dictionaryWithObjectsAndKeys:
459                                                                                                                   policyVersioning, @"versioning", 
460                                                                                                                   [policyQuota stringValue], @"quota", 
461                                                                                                                   nil] 
462                                                                                                         metadata:pithosContainer.metadata 
463                                                                                                           update:NO];
464             applyMetadataContainerRequest.delegate = self;
465             applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
466             applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
467             applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
468                                                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
469                                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
470                                                       NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
471                                                       NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
472                                                       nil];
473             [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
474         }
475     }
476 }
477
478 - (void)refreshInfo {
479     @synchronized(self) {
480         if (refreshMetadataContainerRequest == nil) {
481             refreshMetadataContainerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
482                                                                                                containerName:pithosContainer.name];
483             refreshMetadataContainerRequest.delegate = self;
484             refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
485             refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
486             refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
487                                                         [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
488                                                         [NSNumber numberWithUnsignedInteger:10], @"retries", 
489                                                         NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
490                                                         NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
491                                                         nil];
492             if (!sharingAccount)
493                 refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
494             [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
495         }
496     }
497 }
498
499 #pragma mark -
500 #pragma mark Actions
501
502 - (void)showPithosNodeInfo:(id)sender {
503     if (!pithosNodeInfoController) {
504         pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
505         [self refreshInfo];
506     }
507     [pithosNodeInfoController showWindow:sender];
508     [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
509     [NSApp activateIgnoringOtherApps:YES];
510 }
511
512 @end