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];
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;
223 #pragma mark ASIHTTPRequestDelegate
225 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
226 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
227 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
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];
236 NSError *error = [containerRequest error];
238 message = [NSString stringWithFormat:@"Container listing failed: %@", error];
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];
245 [newChildren release];
247 [containerRequest release];
248 containerRequest = nil;
252 @synchronized(self) {
253 freshness = PithosNodeStateRefreshNeeded;
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];
269 NSArray *someObjects = [containerRequest objects];
270 if (objects == nil) {
271 objects = [[NSMutableArray alloc] initWithArray:someObjects];
273 [objects addObjectsFromArray:someObjects];
275 if ([someObjects count] < 10000) {
276 if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !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) {
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
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];
296 node.shared = shared;
297 node.sharingAccount = sharingAccount;
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];
310 node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
311 [newChildren addObject:node];
313 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
314 PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithos:pithos
315 pithosContainer:pithosContainer
316 pithosObject:object] autorelease];
318 node.shared = shared;
319 node.sharingAccount = sharingAccount;
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;
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];
350 [newChildren addObject:node];
354 [[children objectsAtIndexes:
355 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
356 if ([keptNodes containsIndex:idx])
359 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
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;
367 @synchronized(self) {
368 freshness = PithosNodeStateRefreshFinished;
370 // Notify observers that children are updated
371 [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
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 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",
397 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
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 // Notify observers that children are updated
411 [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
413 [self containerRequestFailed:containerRequest];
418 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
419 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
420 NSLog(@"URL: %@", [request url]);
421 NSLog(@"cached: %d", [request didUseCachedResponse]);
423 if ([request isEqualTo:applyMetadataContainerRequest]) {
424 @synchronized(self) {
425 [applyMetadataContainerRequest release];
426 applyMetadataContainerRequest = nil;
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;
440 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
441 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
442 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
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;
451 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
452 @synchronized(self) {
453 [refreshMetadataContainerRequest release];
454 refreshMetadataContainerRequest = newRequest;
457 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
459 if ([request isEqualTo:applyMetadataContainerRequest]) {
460 dispatch_async(dispatch_get_main_queue(), ^{
461 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
463 @synchronized(self) {
464 [applyMetadataContainerRequest release];
465 applyMetadataContainerRequest = nil;
467 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
468 dispatch_async(dispatch_get_main_queue(), ^{
469 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
471 @synchronized(self) {
472 [refreshMetadataContainerRequest release];
473 refreshMetadataContainerRequest = nil;
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",
493 metadata:pithosContainer.metadata
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",
504 [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
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",
523 refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
524 [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
532 - (void)showPithosNodeInfo:(id)sender {
533 if (!pithosNodeInfoController) {
534 pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
537 [pithosNodeInfoController showWindow:sender];
538 [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
539 [NSApp activateIgnoringOtherApps:YES];