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;
74 - (id)initWithPithos:(ASIPithos *)aPithos pithosContainer:(ASIPithosContainer *)aPithosContainer {
75 return [self initWithPithos:aPithos pithosContainer:aPithosContainer icon:nil];
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];
84 - (id)initWithPithos:(ASIPithos *)aPithos containerName:(NSString *)aContainerName {
85 return [self initWithPithos:aPithos containerName:aContainerName icon:nil];
89 [containerRequest clearDelegatesAndCancel];
90 [containerRequest release];
91 [refreshMetadataContainerRequest clearDelegatesAndCancel];
92 [refreshMetadataContainerRequest release];
93 [applyMetadataContainerRequest clearDelegatesAndCancel];
94 [applyMetadataContainerRequest release];
95 [policyQuota release];
96 [policyVersioning release];
99 [pithosContainer release];
105 #pragma mark Properties
107 - (void)setPithos:(ASIPithos *)aPithos {
108 if (aPithos && ![aPithos isEqualTo:pithos]) {
110 pithos = [aPithos retain];
118 url = [[NSString alloc] initWithFormat:@"%@/%@%@",
119 (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL),
120 pithosContainer.name,
121 (shared ? @"?shared" : @"")];
125 - (NSArray *)children {
126 @synchronized(self) {
128 case PithosNodeStateFresh:
130 case PithosNodeStateRefreshNeeded:
131 freshness = PithosNodeStateRefreshing;
132 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
133 containerName:pithosContainer.name
143 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
144 else if (!forcedRefresh)
145 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
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",
155 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
157 case PithosNodeStateRefreshing:
159 case PithosNodeStateRefreshFinished:
162 children = newChildren;
165 freshness = PithosNodeStateFresh;
173 - (NSString *)displayName {
174 return [[pithosContainer.name copy] autorelease];
177 - (void)setDisplayName:(NSString *)aDisplayName {
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];
187 icon = [sharedIcon retain];
192 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
193 if (![pithosContainer isEqualTo:aPithosContainer]) {
194 [pithosContainer release];
195 pithosContainer = [aPithosContainer retain];
197 if (pithosContainer.policy) {
198 self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
199 self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
201 self.policyVersioning = @"manual";
202 self.policyQuota = [NSNumber numberWithLongLong:0];
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;
221 #pragma mark ASIHTTPRequestDelegate
223 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
226 NSError *error = [containerRequest error];
228 message = [NSString stringWithFormat:@"Container listing %@ failed: %@", containerRequest.url, [error localizedDescription]];
230 message = [NSString stringWithFormat:@"Container listing %@ failed: (%d) %@",
231 containerRequest.url, containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
232 dispatch_async(dispatch_get_main_queue(), ^{
233 [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
235 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
237 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
238 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
239 [containerRequest release];
240 containerRequest = newContainerRequest;
241 [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
243 [newChildren release];
245 [containerRequest release];
246 containerRequest = nil;
250 @synchronized(self) {
251 freshness = PithosNodeStateRefreshNeeded;
257 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
259 DLog(@"List container finished: %@", [containerRequest url]);
260 DLog(@"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];
267 NSArray *someObjects = [containerRequest objects];
268 if (objects == nil) {
269 objects = [[NSMutableArray alloc] initWithArray:someObjects];
271 [objects addObjectsFromArray:someObjects];
273 if ([someObjects count] < 10000) {
274 if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
276 DLog(@"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) {
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
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];
294 node.shared = shared;
295 node.sharingAccount = sharingAccount;
296 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
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];
309 node.pithosObject.allowedTo = @"read";
310 [newChildren addObject:node];
312 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
313 PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos
314 pithosContainer:pithosContainer
315 pithosObject:object] autorelease];
317 node.shared = shared;
318 node.sharingAccount = sharingAccount;
319 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
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];
331 [newChildren addObject:node];
333 PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithos:pithos
334 pithosContainer:pithosContainer
335 pithosObject:object] autorelease];
337 node.shared = shared;
338 node.sharingAccount = sharingAccount;
339 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
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];
351 [newChildren addObject:node];
355 [[children objectsAtIndexes:
356 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
357 if ([keptNodes containsIndex:idx])
360 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
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;
368 @synchronized(self) {
369 freshness = PithosNodeStateRefreshFinished;
371 [self postChildrenUpdatedNotificationName];
373 [containerRequest release];
374 // Do an additional request to fetch more objects
375 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
376 containerName:pithosContainer.name
378 marker:[[someObjects lastObject] name]
386 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
387 else if (!forcedRefresh)
388 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
389 containerRequest.delegate = self;
390 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
391 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
392 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
393 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
394 [NSNumber numberWithUnsignedInteger:10], @"retries",
395 NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector",
396 NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector",
398 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
400 } else if (containerRequest.responseStatusCode == 304) {
401 // Container is not modified, so existing children can be reused
402 [containerRequest release];
403 containerRequest = nil;
407 @synchronized(self) {
408 freshness = PithosNodeStateRefreshFinished;
410 [self postChildrenUpdatedNotificationName];
412 [self containerRequestFailed:containerRequest];
417 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
419 DLog(@"URL: %@", [request url]);
420 DLog(@"cached: %d", [request didUseCachedResponse]);
422 if ([request isEqualTo:applyMetadataContainerRequest]) {
423 @synchronized(self) {
424 [applyMetadataContainerRequest release];
425 applyMetadataContainerRequest = nil;
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;
439 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
441 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
443 ASIPithosContainerRequest *newRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:request];
444 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
445 if ([request isEqualTo:applyMetadataContainerRequest]) {
446 @synchronized(self) {
447 [applyMetadataContainerRequest release];
448 applyMetadataContainerRequest = newRequest;
450 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
451 @synchronized(self) {
452 [refreshMetadataContainerRequest release];
453 refreshMetadataContainerRequest = newRequest;
456 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
457 [newRequest release];
459 if ([request isEqualTo:applyMetadataContainerRequest]) {
460 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
461 @synchronized(self) {
462 [applyMetadataContainerRequest release];
463 applyMetadataContainerRequest = nil;
465 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
466 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
467 @synchronized(self) {
468 [refreshMetadataContainerRequest release];
469 refreshMetadataContainerRequest = nil;
480 @synchronized(self) {
481 if (applyMetadataContainerRequest == nil) {
482 [[pithosNodeInfoController window] makeFirstResponder:nil];
483 applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithos
484 containerName:pithosContainer.name
485 policy:[NSDictionary dictionaryWithObjectsAndKeys:
486 policyVersioning, @"versioning",
487 [policyQuota stringValue], @"quota",
489 metadata:pithosContainer.metadata
491 applyMetadataContainerRequest.delegate = self;
492 applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
493 applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
494 applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
495 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
496 [NSNumber numberWithUnsignedInteger:10], @"retries",
497 NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector",
498 NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector",
500 [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
505 - (void)refreshInfo {
506 @synchronized(self) {
507 if (refreshMetadataContainerRequest == nil) {
508 refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
509 containerName:pithosContainer.name] retain];
510 refreshMetadataContainerRequest.delegate = self;
511 refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
512 refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
513 refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
514 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
515 [NSNumber numberWithUnsignedInteger:10], @"retries",
516 NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector",
517 NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector",
520 refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
521 [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
529 - (void)showPithosNodeInfo:(id)sender {
530 if (!pithosNodeInfoController) {
531 pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
534 [pithosNodeInfoController showWindow:sender];
535 [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
536 [NSApp activateIgnoringOtherApps:YES];