2 // PithosContainerNode.m
5 // Copyright 2011-2013 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 "PithosAccount.h"
48 #import "PithosContainerNodeInfoController.h"
49 #import "PithosActivityFacility.h"
51 static NSImage *sharedIcon = nil;
53 @implementation PithosContainerNode
54 @synthesize pithosContainer, containerRequest, prefix, applyMetadataContainerRequest, refreshMetadataContainerRequest;
55 @synthesize policyVersioning, policyQuota;
58 if (self == [PithosContainerNode class])
59 sharedIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)];
63 #pragma mark Object Lifecycle
65 - (id)initWithPithosAccountManager:(PithosAccount *)aPithosAccountManager pithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
66 if ((self = [super initWithPithosAccountManager:aPithosAccountManager])) {
67 self.pithosContainer = aPithosContainer;
73 - (id)initWithPithosAccountManager:(PithosAccount *)aPithosAccountManager pithosContainer:(ASIPithosContainer *)aPithosContainer {
74 return [self initWithPithosAccountManager:aPithosAccountManager pithosContainer:aPithosContainer icon:nil];
77 - (id)initWithPithosAccountManager:(PithosAccount *)aPithosAccountManager containerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
78 ASIPithosContainer *container = [ASIPithosContainer container];
79 container.name = aContainerName;
80 return [self initWithPithosAccountManager:aPithosAccountManager pithosContainer:container icon:anIcon];
83 - (id)initWithPithosAccountManager:(PithosAccount *)aPithosAccountManager containerName:(NSString *)aContainerName {
84 return [self initWithPithosAccountManager:aPithosAccountManager containerName:aContainerName icon:nil];
88 [containerRequest clearDelegatesAndCancel];
89 [refreshMetadataContainerRequest clearDelegatesAndCancel];
90 [applyMetadataContainerRequest clearDelegatesAndCancel];
94 #pragma mark Properties
97 return [NSString stringWithFormat:@"@container@%@/%@%@",
98 (sharingAccount ? sharingAccount : pithosAccountManager.pithos.authUser),
100 (shared ? @"?shared" : @"")];
103 - (NSArray *)children {
104 @synchronized(self) {
106 case PithosNodeStateFresh:
108 case PithosNodeStateRefreshNeeded:
109 freshness = PithosNodeStateRefreshing;
110 self.containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithosAccountManager.pithos
111 containerName:pithosContainer.name
121 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithosAccountManager.pithos];
122 else if (!forcedRefresh)
123 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
124 containerRequest.delegate = self;
125 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
126 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
127 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
128 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
129 [NSNumber numberWithUnsignedInteger:10], @"retries",
130 NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector",
131 NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector",
133 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
135 case PithosNodeStateRefreshing:
137 case PithosNodeStateRefreshFinished:
139 children = newChildren;
142 freshness = PithosNodeStateFresh;
150 - (NSString *)displayName {
151 return [pithosContainer.name copy];
154 - (void)setDisplayName:(NSString *)aDisplayName {
159 if ([pithosContainer.name isEqualToString:@"pithos"])
160 icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
161 else if ([pithosContainer.name isEqualToString:@"trash"])
162 icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
169 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
170 if (![pithosContainer isEqualTo:aPithosContainer]) {
171 pithosContainer = aPithosContainer;
173 if (pithosContainer.policy) {
174 self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
175 self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
177 self.policyVersioning = @"manual";
178 self.policyQuota = [NSNumber numberWithLongLong:0];
182 - (void)setLimitedPithosContainer:(ASIPithosContainer *)aPithosContainer {
183 if (![pithosContainer isEqualTo:aPithosContainer]) {
184 self.pithosContainer.name = aPithosContainer.name;
185 self.pithosContainer.count = aPithosContainer.count;
186 self.pithosContainer.bytes = aPithosContainer.bytes;
187 self.pithosContainer.lastModified = aPithosContainer.lastModified;
188 self.pithosContainer.untilTimestamp = aPithosContainer.untilTimestamp;
189 if (!pithosNodeInfoController) {
190 self.pithosContainer.policy = aPithosContainer.policy;
191 self.pithosContainer = pithosContainer;
197 #pragma mark ASIHTTPRequestDelegate
199 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
202 NSError *error = [containerRequest error];
204 message = [NSString stringWithFormat:@"Container listing %@ failed: %@", containerRequest.url, [error localizedDescription]];
206 message = [NSString stringWithFormat:@"Container listing %@ failed: (%d) %@",
207 containerRequest.url, containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
208 dispatch_async(dispatch_get_main_queue(), ^{
209 [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
211 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
213 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
214 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
215 self.containerRequest = newContainerRequest;
216 [[PithosUtilities prepareRequest:containerRequest priority:[[containerRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
219 self.containerRequest = nil;
221 @synchronized(self) {
222 freshness = PithosNodeStateRefreshNeeded;
228 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
230 DLog(@"List container finished: %@", [containerRequest url]);
231 DLog(@"Cached: %d", [containerRequest didUseCachedResponse]);
232 if (containerRequest.responseStatusCode == 200) {
233 if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
234 pithosContainer.blockHash = [containerRequest blockHash];
235 pithosContainer.blockSize = [containerRequest blockSize];
238 NSMutableArray *objects = [containerRequest.userInfo objectForKey:@"objects"];
239 NSArray *someObjects = [containerRequest objects];
240 if (objects == nil) {
241 objects = [NSMutableArray arrayWithArray:someObjects];
243 [objects addObjectsFromArray:someObjects];
245 if ([someObjects count] < 10000) {
246 if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
248 DLog(@"using newChildren");
249 newChildren = [[NSMutableArray alloc] init];
250 NSArray *objectNames = [objects valueForKey:@"name"];
251 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
252 BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
253 for (ASIPithosObject *object in objects) {
255 ([object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]] &&
256 ([object.name length] > [((PithosSubdirNode *)self).prefix length] + 1))) {
257 // The check above removes false objects due to trailing slash or same prefix
259 NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
260 if ((sameNameObjectIndex == NSNotFound) ||
261 ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) {
262 PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithosAccountManager:pithosAccountManager
263 pithosContainer:pithosContainer
264 pithosObject:object];
266 node.shared = shared;
267 node.sharingAccount = sharingAccount;
268 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
270 NSUInteger oldIndex = [children indexOfObject:node];
271 if (oldIndex != NSNotFound) {
272 // Use the same pointer value, if possible
273 node = [children objectAtIndex:oldIndex];
274 node.pithosContainer = pithosContainer;
275 // node.pithosObject = object;
276 [node setLimitedPithosObject:object];
277 [keptNodes addIndex:oldIndex];
281 node.pithosObject.allowedTo = @"read";
282 [newChildren addObject:node];
284 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
285 PithosSubdirNode *node = [[PithosSubdirNode alloc] initWithPithosAccountManager:pithosAccountManager
286 pithosContainer:pithosContainer
287 pithosObject:object];
289 node.shared = shared;
290 node.sharingAccount = sharingAccount;
291 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
293 NSUInteger oldIndex = [children indexOfObject:node];
294 if (oldIndex != NSNotFound) {
295 // Use the same pointer value, if possible
296 node = [children objectAtIndex:oldIndex];
297 node.pithosContainer = pithosContainer;
298 // node.pithosObject = object;
299 [node setLimitedPithosObject:object];
300 [keptNodes addIndex:oldIndex];
303 [newChildren addObject:node];
305 PithosObjectNode *node = [[PithosObjectNode alloc] initWithPithosAccountManager:pithosAccountManager
306 pithosContainer:pithosContainer
307 pithosObject:object];
309 node.shared = shared;
310 node.sharingAccount = sharingAccount;
311 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
313 NSUInteger oldIndex = [children indexOfObject:node];
314 if (oldIndex != NSNotFound) {
315 // Use the same pointer value, if possible
316 node = [children objectAtIndex:oldIndex];
317 node.pithosContainer = pithosContainer;
318 // node.pithosObject = object;
319 [node setLimitedPithosObject:object];
320 [keptNodes addIndex:oldIndex];
323 [newChildren addObject:node];
327 [[children objectsAtIndexes:
328 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
329 if ([keptNodes containsIndex:idx])
332 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
334 // Else cache was used and all results were fetched during this request, so existing children can be reused
335 self.containerRequest = nil;
337 @synchronized(self) {
338 freshness = PithosNodeStateRefreshFinished;
340 [self postChildrenUpdatedNotificationName];
342 // Do an additional request to fetch more objects
343 self.containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithosAccountManager.pithos
344 containerName:pithosContainer.name
346 marker:[[someObjects lastObject] name]
354 [containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithosAccountManager.pithos];
355 else if (!forcedRefresh)
356 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
357 containerRequest.delegate = self;
358 containerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
359 containerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
360 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
361 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
362 [NSNumber numberWithUnsignedInteger:10], @"retries",
363 NSStringFromSelector(@selector(containerRequestFinished:)), @"didFinishSelector",
364 NSStringFromSelector(@selector(containerRequestFailed:)), @"didFailSelector",
367 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
369 } else if (containerRequest.responseStatusCode == 304) {
370 // Container is not modified, so existing children can be reused
371 self.containerRequest = nil;
373 @synchronized(self) {
374 freshness = PithosNodeStateRefreshFinished;
376 [self postChildrenUpdatedNotificationName];
378 [self containerRequestFailed:containerRequest];
383 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
385 DLog(@"URL: %@", [request url]);
386 DLog(@"cached: %d", [request didUseCachedResponse]);
388 if ([request isEqualTo:applyMetadataContainerRequest]) {
389 @synchronized(self) {
390 self.applyMetadataContainerRequest = nil;
393 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
394 [[pithosNodeInfoController window] makeFirstResponder:nil];
395 self.pithosContainer = [refreshMetadataContainerRequest container];
396 @synchronized(self) {
397 self.refreshMetadataContainerRequest = nil;
403 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
405 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
407 ASIPithosContainerRequest *newRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:request];
408 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
409 if ([request isEqualTo:applyMetadataContainerRequest]) {
410 @synchronized(self) {
411 self.applyMetadataContainerRequest = newRequest;
413 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
414 @synchronized(self) {
415 self.refreshMetadataContainerRequest = newRequest;
418 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
420 if ([request isEqualTo:applyMetadataContainerRequest]) {
421 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
422 @synchronized(self) {
423 self.applyMetadataContainerRequest = nil;
425 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
426 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
427 @synchronized(self) {
428 self.refreshMetadataContainerRequest = nil;
439 @synchronized(self) {
440 if (applyMetadataContainerRequest == nil) {
441 [[pithosNodeInfoController window] makeFirstResponder:nil];
442 self.applyMetadataContainerRequest = [ASIPithosContainerRequest updateContainerMetadataRequestWithPithos:pithosAccountManager.pithos
443 containerName:pithosContainer.name
444 policy:[NSDictionary dictionaryWithObjectsAndKeys:
445 policyVersioning, @"versioning",
446 [policyQuota stringValue], @"quota",
448 metadata:pithosContainer.metadata
450 applyMetadataContainerRequest.delegate = self;
451 applyMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
452 applyMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
453 applyMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
454 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
455 [NSNumber numberWithUnsignedInteger:10], @"retries",
456 NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector",
457 NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector",
459 [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
464 - (void)refreshInfo {
465 @synchronized(self) {
466 if (refreshMetadataContainerRequest == nil) {
467 self.refreshMetadataContainerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithosAccountManager.pithos
468 containerName:pithosContainer.name];
469 refreshMetadataContainerRequest.delegate = self;
470 refreshMetadataContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
471 refreshMetadataContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
472 refreshMetadataContainerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
473 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
474 [NSNumber numberWithUnsignedInteger:10], @"retries",
475 NSStringFromSelector(@selector(containerMetadataRequestFinished:)), @"didFinishSelector",
476 NSStringFromSelector(@selector(containerMetadataRequestFailed:)), @"didFailSelector",
479 refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
480 [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
488 - (void)showPithosNodeInfo:(id)sender {
489 if (!pithosNodeInfoController) {
490 pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
493 [pithosNodeInfoController showWindow:sender];
494 [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
495 [NSApp activateIgnoringOtherApps:YES];