Support changes in container and object metadata.
[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 - (void)setLimitedPithosContainer:(ASIPithosContainer *)aPithosContainer {
209     if (![pithosContainer isEqualTo:aPithosContainer]) {
210         self.pithosContainer.name = aPithosContainer.name;
211         self.pithosContainer.count = aPithosContainer.count;
212         self.pithosContainer.bytes = aPithosContainer.bytes;
213         self.pithosContainer.lastModified = aPithosContainer.lastModified;
214         self.pithosContainer.untilTimestamp = aPithosContainer.untilTimestamp;
215         if (!pithosNodeInfoController) {
216             self.pithosContainer.policy = aPithosContainer.policy;
217             self.pithosContainer = pithosContainer;
218         }
219     }
220 }
221
222 #pragma mark -
223 #pragma mark ASIHTTPRequestDelegate
224
225 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
226     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
227     NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
228     if (retries > 0) {
229         ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
230         [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
231         [containerRequest release];
232         containerRequest = newContainerRequest;
233         [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
234     } else {
235         NSString *message;
236         NSError *error = [containerRequest error];
237         if (error)
238             message = [NSString stringWithFormat:@"Container listing failed: %@", error];
239         else
240             message = [NSString stringWithFormat:@"Container listing failed: (%d) %@", 
241                        containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
242         dispatch_async(dispatch_get_main_queue(), ^{
243             [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
244         });
245         [newChildren release];
246         newChildren = nil;
247         [containerRequest release];
248         containerRequest = nil;
249         [objects release];
250         objects = nil;
251         forcedRefresh = NO;
252         @synchronized(self) {
253             freshness = PithosNodeStateRefreshNeeded;
254         }
255     }
256     [pool drain];
257 }
258
259 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
260     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
261     NSLog(@"List container finished: %@", [containerRequest url]);
262     NSLog(@"Cached: %d", [containerRequest didUseCachedResponse]);
263     if (containerRequest.responseStatusCode == 200) {
264         if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
265             pithosContainer.blockHash = [containerRequest blockHash];
266             pithosContainer.blockSize = [containerRequest blockSize];
267         }
268     
269         NSArray *someObjects = [containerRequest objects];
270         if (objects == nil) {
271             objects = [[NSMutableArray alloc] initWithArray:someObjects];
272         } else {
273             [objects addObjectsFromArray:someObjects];
274         }
275         if ([someObjects count] < 10000) {
276             if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
277                 // Save new children
278                 NSLog(@"using newChildren");
279                 newChildren = [[NSMutableArray alloc] init];
280                 NSArray *objectNames = [objects valueForKey:@"name"];
281                 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
282                 BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
283                 for (ASIPithosObject *object in objects) {
284                     if (!isSubdirNode || 
285                         ([object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]] &&
286                          ([object.name length] > [((PithosSubdirNode *)self).prefix length] + 1))) {
287                         // The check above removes false objects due to trailing slash or same prefix
288                         if (object.subdir) {
289                             NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
290                             if ((sameNameObjectIndex == NSNotFound) || 
291                                 ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) {
292                                 PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos 
293                                                                                    pithosContainer:pithosContainer 
294                                                                                       pithosObject:object] autorelease];
295                                 node.parent = self;
296                                 node.shared = shared;
297                                 node.sharingAccount = sharingAccount;
298                                 if (children) {
299                                     NSUInteger oldIndex = [children indexOfObject:node];
300                                     if (oldIndex != NSNotFound) {
301                                         // Use the same pointer value, if possible
302                                         node = [children objectAtIndex:oldIndex];
303                                         node.pithosContainer = pithosContainer;
304 //                                        node.pithosObject = object;
305                                         [node setLimitedPithosObject:object];
306                                         [keptNodes addIndex:oldIndex];
307                                     }
308                                 }
309                                 if (sharingAccount)
310                                     node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
311                                 [newChildren addObject:node];
312                             }
313                         } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
314                             PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos 
315                                                                                pithosContainer:pithosContainer 
316                                                                                   pithosObject:object] autorelease];
317                             node.parent = self;
318                             node.shared = shared;
319                             node.sharingAccount = sharingAccount;
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                             if (children) {
340                                 NSUInteger oldIndex = [children indexOfObject:node];
341                                 if (oldIndex != NSNotFound) {
342                                     // Use the same pointer value, if possible
343                                     node = [children objectAtIndex:oldIndex];
344                                     node.pithosContainer = pithosContainer;
345 //                                    node.pithosObject = object;
346                                     [node setLimitedPithosObject:object];
347                                     [keptNodes addIndex:oldIndex];
348                                 }
349                             }
350                             [newChildren addObject:node];                                
351                         }
352                     }
353                 }
354                 [[children objectsAtIndexes:
355                   [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
356                     if ([keptNodes containsIndex:idx])
357                         return NO;
358                     return YES;
359                 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
360             }
361             // Else cache was used and all results were fetched during this request, so existing children can be reused
362             [containerRequest release];
363             containerRequest = nil;
364             [objects release];
365             objects = nil;
366             forcedRefresh = NO;
367             @synchronized(self) {
368                 freshness = PithosNodeStateRefreshFinished;
369             }
370             // Notify observers that children are updated
371             [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
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         // Notify observers that children are updated
411         [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
412     } else {
413         [self containerRequestFailed:containerRequest];
414     }
415     [pool drain];
416 }
417
418 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
419     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
420     NSLog(@"URL: %@", [request url]);
421     NSLog(@"cached: %d", [request didUseCachedResponse]);
422     
423     if ([request isEqualTo:applyMetadataContainerRequest]) {
424         @synchronized(self) {
425             [applyMetadataContainerRequest release];
426             applyMetadataContainerRequest = nil;
427         }
428         [self refreshInfo];
429     } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
430         [[pithosNodeInfoController window] makeFirstResponder:nil];
431         self.pithosContainer = [refreshMetadataContainerRequest container];
432         @synchronized(self) {
433             [refreshMetadataContainerRequest release];
434             refreshMetadataContainerRequest = nil;
435         }
436     }
437     [pool drain];
438 }
439
440 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
441     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
442     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
443     if (retries > 0) {
444         ASIPithosContainerRequest *newRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:request];
445         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
446         if ([request isEqualTo:applyMetadataContainerRequest]) {
447             @synchronized(self) {
448                 [applyMetadataContainerRequest release];
449                 applyMetadataContainerRequest = newRequest;
450             }
451         } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
452             @synchronized(self) {
453                 [refreshMetadataContainerRequest release];
454                 refreshMetadataContainerRequest = newRequest;
455             }
456         }
457         [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
458     } else {
459         if ([request isEqualTo:applyMetadataContainerRequest]) {
460             dispatch_async(dispatch_get_main_queue(), ^{
461                 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
462             });
463             @synchronized(self) {
464                 [applyMetadataContainerRequest release];
465                 applyMetadataContainerRequest = nil;
466             }
467         } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
468             dispatch_async(dispatch_get_main_queue(), ^{
469                 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
470             });
471             @synchronized(self) {
472                 [refreshMetadataContainerRequest release];
473                 refreshMetadataContainerRequest = nil;
474             }
475         }
476     }
477     [pool drain];
478 }
479
480 #pragma mark -
481 #pragma mark Info
482
483 - (void)applyInfo {
484     @synchronized(self) {
485         if (applyMetadataContainerRequest == nil) {
486             [[pithosNodeInfoController window] makeFirstResponder:nil];
487             applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithos 
488                                                                                                    containerName:pithosContainer.name 
489                                                                                                           policy:[NSDictionary dictionaryWithObjectsAndKeys:
490                                                                                                                   policyVersioning, @"versioning", 
491                                                                                                                   [policyQuota stringValue], @"quota", 
492                                                                                                                   nil] 
493                                                                                                         metadata:pithosContainer.metadata 
494                                                                                                           update:NO] retain];
495             applyMetadataContainerRequest.delegate = self;
496             applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
497             applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
498             applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
499                                                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
500                                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
501                                                       NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
502                                                       NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
503                                                       nil];
504             [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
505         }
506     }
507 }
508
509 - (void)refreshInfo {
510     @synchronized(self) {
511         if (refreshMetadataContainerRequest == nil) {
512             refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos 
513                                                                                                containerName:pithosContainer.name] retain];
514             refreshMetadataContainerRequest.delegate = self;
515             refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
516             refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
517             refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
518                                                         [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
519                                                         [NSNumber numberWithUnsignedInteger:10], @"retries", 
520                                                         NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector", 
521                                                         NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector", 
522                                                         nil];
523             refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
524             [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
525         }
526     }
527 }
528
529 #pragma mark -
530 #pragma mark Actions
531
532 - (void)showPithosNodeInfo:(id)sender {
533     if (!pithosNodeInfoController) {
534         pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
535         [self refreshInfo];
536     }
537     [pithosNodeInfoController showWindow:sender];
538     [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
539     [NSApp activateIgnoringOtherApps:YES];
540 }
541
542 @end