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