2 // PithosContainerNode.m
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
38 #import "PithosContainerNode.h"
39 #import "PithosObjectNode.h"
40 #import "PithosSubdirNode.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"
50 static NSImage *sharedIcon = nil;
52 @implementation PithosContainerNode
53 @synthesize pithos, pithosContainer, prefix;
54 @synthesize policyVersioning, policyQuota;
57 if (self == [PithosContainerNode class])
58 sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain];
62 #pragma mark Object Lifecycle
64 - (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
65 if ((self = [super init])) {
66 self.pithos = aPithos;
67 self.pithosContainer = aPithosContainer;
70 childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated";
75 - (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer {
76 return [self initWithPithos:aPithos pithosContainer:aPithosContainer icon:nil];
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];
85 - (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName {
86 return [self initWithPithos:aPithos containerName:aContainerName icon:nil];
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];
101 [pithosContainer release];
107 #pragma mark Properties
109 - (void)setPithos:(ASIPithos *)aPithos {
110 if (aPithos && ![aPithos isEqualTo:pithos]) {
112 pithos = [aPithos retain];
120 url = [[NSString alloc] initWithFormat:@"%@/%@%@",
121 (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL),
122 pithosContainer.name,
123 (shared ? @"?shared" : @"")];
127 - (NSArray *)children {
128 @synchronized(self) {
130 case PithosNodeStateFresh:
132 case PithosNodeStateRefreshNeeded:
133 freshness = PithosNodeStateRefreshing;
134 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
135 containerName:pithosContainer.name
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",
156 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
157 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
159 case PithosNodeStateRefreshing:
161 case PithosNodeStateRefreshFinished:
164 children = newChildren;
167 freshness = PithosNodeStateFresh;
175 - (NSString *)displayName {
176 return [[pithosContainer.name copy] autorelease];
179 - (void)setDisplayName:(NSString *)aDisplayName {
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];
189 icon = [sharedIcon retain];
194 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
195 if (![pithosContainer isEqualTo:aPithosContainer]) {
196 [pithosContainer release];
197 pithosContainer = [aPithosContainer retain];
199 if (pithosContainer.policy) {
200 self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
201 self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
203 self.policyVersioning = @"manual";
204 self.policyQuota = [NSNumber numberWithLongLong:0];
209 #pragma mark ASIHTTPRequestDelegate
211 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
212 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
213 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
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];
222 NSError *error = [containerRequest error];
224 message = [NSString stringWithFormat:@"Container listing failed: %@", error];
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];
231 [newChildren release];
233 [containerRequest release];
234 containerRequest = nil;
238 @synchronized(self) {
239 freshness = PithosNodeStateRefreshNeeded;
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];
255 NSArray *someObjects = [containerRequest objects];
256 if (objects == nil) {
257 objects = [[NSMutableArray alloc] initWithArray:someObjects];
259 [objects addObjectsFromArray:someObjects];
261 if ([someObjects count] < 10000) {
262 if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !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) {
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
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];
282 node.shared = shared;
283 node.sharingAccount = sharingAccount;
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];
295 node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
296 [newChildren addObject:node];
298 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
299 PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos
300 pithosContainer:pithosContainer
301 pithosObject:object] autorelease];
303 node.shared = shared;
304 node.sharingAccount = sharingAccount;
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];
315 [newChildren addObject:node];
317 PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithos:pithos
318 pithosContainer:pithosContainer
319 pithosObject:object] autorelease];
321 node.shared = shared;
322 node.sharingAccount = sharingAccount;
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];
333 [newChildren addObject:node];
337 [[children objectsAtIndexes:
338 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
339 if ([keptNodes containsIndex:idx])
342 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
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;
350 @synchronized(self) {
351 freshness = PithosNodeStateRefreshFinished;
353 // Notify observers that children are updated
354 [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
356 [containerRequest release];
357 // Do an additional request to fetch more objects
358 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
359 containerName:pithosContainer.name
361 marker:[[someObjects lastObject] name]
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",
380 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
381 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
383 } else if (containerRequest.responseStatusCode == 304) {
384 // Container is not modified, so existing children can be reused
385 [containerRequest release];
386 containerRequest = nil;
390 @synchronized(self) {
391 freshness = PithosNodeStateRefreshFinished;
393 // Notify observers that children are updated
394 [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
396 [self containerRequestFailed:containerRequest];
401 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
402 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
403 NSLog(@"URL: %@", [request url]);
404 NSLog(@"cached: %d", [request didUseCachedResponse]);
406 if ([request isEqualTo:applyMetadataContainerRequest]) {
407 @synchronized(self) {
408 [applyMetadataContainerRequest release];
409 applyMetadataContainerRequest = nil;
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;
423 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
424 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
425 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
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;
434 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
435 @synchronized(self) {
436 [refreshMetadataContainerRequest release];
437 refreshMetadataContainerRequest = newRequest;
440 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
442 if ([request isEqualTo:applyMetadataContainerRequest]) {
443 dispatch_async(dispatch_get_main_queue(), ^{
444 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
446 @synchronized(self) {
447 [applyMetadataContainerRequest release];
448 applyMetadataContainerRequest = nil;
450 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
451 dispatch_async(dispatch_get_main_queue(), ^{
452 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
454 @synchronized(self) {
455 [refreshMetadataContainerRequest release];
456 refreshMetadataContainerRequest = nil;
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",
476 metadata:pithosContainer.metadata
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",
487 [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
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",
506 refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
507 [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
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];