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)];
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 [refreshMetadataContainerRequest clearDelegatesAndCancel];
91 [applyMetadataContainerRequest clearDelegatesAndCancel];
95 #pragma mark Properties
97 - (void)setPithos:(ASIPithos *)aPithos {
98 if (aPithos && ![aPithos isEqualTo:pithos]) {
106 url = [[NSString alloc] initWithFormat:@"%@/%@%@",
107 (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL),
108 pithosContainer.name,
109 (shared ? @"?shared" : @"")];
113 - (NSArray *)children {
114 @synchronized(self) {
116 case PithosNodeStateFresh:
118 case PithosNodeStateRefreshNeeded:
119 freshness = PithosNodeStateRefreshing;
120 containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
121 containerName:pithosContainer.name
131 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
132 else if (!forcedRefresh)
133 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
134 containerRequest.delegate = self;
135 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
136 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
137 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
138 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
139 [NSNumber numberWithUnsignedInteger:10], @"retries",
140 NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector",
141 NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector",
143 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
145 case PithosNodeStateRefreshing:
147 case PithosNodeStateRefreshFinished:
149 children = newChildren;
152 freshness = PithosNodeStateFresh;
160 - (NSString *)displayName {
161 return [pithosContainer.name copy];
164 - (void)setDisplayName:(NSString *)aDisplayName {
169 if ([pithosContainer.name isEqualToString:@"pithos"])
170 icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
171 else if ([pithosContainer.name isEqualToString:@"trash"])
172 icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
179 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
180 if (![pithosContainer isEqualTo:aPithosContainer]) {
181 pithosContainer = aPithosContainer;
183 if (pithosContainer.policy) {
184 self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
185 self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
187 self.policyVersioning = @"manual";
188 self.policyQuota = [NSNumber numberWithLongLong:0];
192 - (void)setLimitedPithosContainer:(ASIPithosContainer *)aPithosContainer {
193 if (![pithosContainer isEqualTo:aPithosContainer]) {
194 self.pithosContainer.name = aPithosContainer.name;
195 self.pithosContainer.count = aPithosContainer.count;
196 self.pithosContainer.bytes = aPithosContainer.bytes;
197 self.pithosContainer.lastModified = aPithosContainer.lastModified;
198 self.pithosContainer.untilTimestamp = aPithosContainer.untilTimestamp;
199 if (!pithosNodeInfoController) {
200 self.pithosContainer.policy = aPithosContainer.policy;
201 self.pithosContainer = pithosContainer;
207 #pragma mark ASIHTTPRequestDelegate
209 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
212 NSError *error = [containerRequest error];
214 message = [NSString stringWithFormat:@"Container listing %@ failed: %@", containerRequest.url, [error localizedDescription]];
216 message = [NSString stringWithFormat:@"Container listing %@ failed: (%d) %@",
217 containerRequest.url, containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
218 dispatch_async(dispatch_get_main_queue(), ^{
219 [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
221 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
223 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
224 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
225 containerRequest = newContainerRequest;
226 [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
229 containerRequest = nil;
232 @synchronized(self) {
233 freshness = PithosNodeStateRefreshNeeded;
239 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
241 DLog(@"List container finished: %@", [containerRequest url]);
242 DLog(@"Cached: %d", [containerRequest didUseCachedResponse]);
243 if (containerRequest.responseStatusCode == 200) {
244 if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
245 pithosContainer.blockHash = [containerRequest blockHash];
246 pithosContainer.blockSize = [containerRequest blockSize];
249 NSArray *someObjects = [containerRequest objects];
250 if (objects == nil) {
251 objects = [[NSMutableArray alloc] initWithArray:someObjects];
253 [objects addObjectsFromArray:someObjects];
255 if ([someObjects count] < 10000) {
256 if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
258 DLog(@"using newChildren");
259 newChildren = [[NSMutableArray alloc] init];
260 NSArray *objectNames = [objects valueForKey:@"name"];
261 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
262 BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
263 for (ASIPithosObject *object in objects) {
265 ([object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]] &&
266 ([object.name length] > [((PithosSubdirNode *)self).prefix length] + 1))) {
267 // The check above removes false objects due to trailing slash or same prefix
269 NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
270 if ((sameNameObjectIndex == NSNotFound) ||
271 ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) {
272 PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithos:pithos
273 pithosContainer:pithosContainer
274 pithosObject:object];
276 node.shared = shared;
277 node.sharingAccount = sharingAccount;
278 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
279 node.pithosAccountManager = pithosAccountManager;
281 NSUInteger oldIndex = [children indexOfObject:node];
282 if (oldIndex != NSNotFound) {
283 // Use the same pointer value, if possible
284 node = [children objectAtIndex:oldIndex];
285 node.pithosContainer = pithosContainer;
286 // node.pithosObject = object;
287 [node setLimitedPithosObject:object];
288 [keptNodes addIndex:oldIndex];
292 node.pithosObject.allowedTo = @"read";
293 [newChildren addObject:node];
295 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
296 PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithos:pithos
297 pithosContainer:pithosContainer
298 pithosObject:object];
300 node.shared = shared;
301 node.sharingAccount = sharingAccount;
302 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
303 node.pithosAccountManager = pithosAccountManager;
305 NSUInteger oldIndex = [children indexOfObject:node];
306 if (oldIndex != NSNotFound) {
307 // Use the same pointer value, if possible
308 node = [children objectAtIndex:oldIndex];
309 node.pithosContainer = pithosContainer;
310 // node.pithosObject = object;
311 [node setLimitedPithosObject:object];
312 [keptNodes addIndex:oldIndex];
315 [newChildren addObject:node];
317 PithosObjectNode *node = [[PithosObjectNode alloc] initWithPithos:pithos
318 pithosContainer:pithosContainer
319 pithosObject:object];
321 node.shared = shared;
322 node.sharingAccount = sharingAccount;
323 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
324 node.pithosAccountManager = pithosAccountManager;
326 NSUInteger oldIndex = [children indexOfObject:node];
327 if (oldIndex != NSNotFound) {
328 // Use the same pointer value, if possible
329 node = [children objectAtIndex:oldIndex];
330 node.pithosContainer = pithosContainer;
331 // node.pithosObject = object;
332 [node setLimitedPithosObject:object];
333 [keptNodes addIndex:oldIndex];
336 [newChildren addObject:node];
340 [[children objectsAtIndexes:
341 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
342 if ([keptNodes containsIndex:idx])
345 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
347 // Else cache was used and all results were fetched during this request, so existing children can be reused
348 containerRequest = nil;
351 @synchronized(self) {
352 freshness = PithosNodeStateRefreshFinished;
354 [self postChildrenUpdatedNotificationName];
356 // Do an additional request to fetch more objects
357 containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos
358 containerName:pithosContainer.name
360 marker:[[someObjects lastObject] name]
368 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
369 else if (!forcedRefresh)
370 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
371 containerRequest.delegate = self;
372 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
373 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
374 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
375 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
376 [NSNumber numberWithUnsignedInteger:10], @"retries",
377 NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector",
378 NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector",
380 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
382 } else if (containerRequest.responseStatusCode == 304) {
383 // Container is not modified, so existing children can be reused
384 containerRequest = nil;
387 @synchronized(self) {
388 freshness = PithosNodeStateRefreshFinished;
390 [self postChildrenUpdatedNotificationName];
392 [self containerRequestFailed:containerRequest];
397 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
399 DLog(@"URL: %@", [request url]);
400 DLog(@"cached: %d", [request didUseCachedResponse]);
402 if ([request isEqualTo:applyMetadataContainerRequest]) {
403 @synchronized(self) {
404 applyMetadataContainerRequest = nil;
407 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
408 [[pithosNodeInfoController window] makeFirstResponder:nil];
409 self.pithosContainer = [refreshMetadataContainerRequest container];
410 @synchronized(self) {
411 refreshMetadataContainerRequest = nil;
417 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
419 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
421 ASIPithosContainerRequest *newRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:request];
422 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
423 if ([request isEqualTo:applyMetadataContainerRequest]) {
424 @synchronized(self) {
425 applyMetadataContainerRequest = newRequest;
427 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
428 @synchronized(self) {
429 refreshMetadataContainerRequest = newRequest;
432 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
434 if ([request isEqualTo:applyMetadataContainerRequest]) {
435 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
436 @synchronized(self) {
437 applyMetadataContainerRequest = nil;
439 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
440 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
441 @synchronized(self) {
442 refreshMetadataContainerRequest = nil;
453 @synchronized(self) {
454 if (applyMetadataContainerRequest == nil) {
455 [[pithosNodeInfoController window] makeFirstResponder:nil];
456 applyMetadataContainerRequest = [ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithos
457 containerName:pithosContainer.name
458 policy:[NSDictionary dictionaryWithObjectsAndKeys:
459 policyVersioning, @"versioning",
460 [policyQuota stringValue], @"quota",
462 metadata:pithosContainer.metadata
464 applyMetadataContainerRequest.delegate = self;
465 applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
466 applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
467 applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
468 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
469 [NSNumber numberWithUnsignedInteger:10], @"retries",
470 NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector",
471 NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector",
473 [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
478 - (void)refreshInfo {
479 @synchronized(self) {
480 if (refreshMetadataContainerRequest == nil) {
481 refreshMetadataContainerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
482 containerName:pithosContainer.name];
483 refreshMetadataContainerRequest.delegate = self;
484 refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
485 refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
486 refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
487 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
488 [NSNumber numberWithUnsignedInteger:10], @"retries",
489 NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector",
490 NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector",
493 refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
494 [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
502 - (void)showPithosNodeInfo:(id)sender {
503 if (!pithosNodeInfoController) {
504 pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
507 [pithosNodeInfoController showWindow:sender];
508 [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
509 [NSApp activateIgnoringOtherApps:YES];