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