2 // PithosContainerNode.m
5 // Copyright 2011 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"
41 #import "ASIPithosContainerRequest.h"
42 #import "ASIPithosContainer.h"
43 #import "ASIPithosObject.h"
44 #import "ASIDownloadCache.h"
45 #import "PithosUtilities.h"
46 #import "PithosContainerNodeInfoController.h"
47 #import "PithosActivityFacility.h"
49 static NSImage *sharedIcon = nil;
51 @implementation PithosContainerNode
52 @synthesize pithosContainer, prefix;
53 @synthesize policyVersioning, policyQuota;
56 if (self == [PithosContainerNode class])
57 sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain];
61 #pragma mark Object Lifecycle
63 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
64 if ((self = [super init])) {
65 self.pithosContainer = aPithosContainer;
68 childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated";
73 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer {
74 return [self initWithPithosContainer:aPithosContainer icon:nil];
77 - (id)initWithContainerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
78 ASIPithosContainer *container = [ASIPithosContainer container];
79 container.name = aContainerName;
80 return [self initWithPithosContainer:container icon:anIcon];
83 - (id)initWithContainerName:(NSString *)aContainerName {
84 return [self initWithContainerName:aContainerName icon:nil];
88 [containerRequest clearDelegatesAndCancel];
89 [containerRequest release];
90 [refreshMetadataContainerRequest clearDelegatesAndCancel];
91 [refreshMetadataContainerRequest release];
92 [applyMetadataContainerRequest clearDelegatesAndCancel];
93 [applyMetadataContainerRequest release];
94 [policyQuota release];
95 [policyVersioning release];
96 [childrenUpdatedNotificationName release];
99 [pithosContainer release];
104 #pragma mark Properties
108 url = [[NSString alloc] initWithFormat:@"%@/%@%@",
109 (sharingAccount ? [ASIPithosRequest storageURLWithAuthUser:sharingAccount] : [ASIPithosRequest storageURL]),
110 pithosContainer.name,
111 (shared ? @"?shared" : @"")];
115 - (NSArray *)children {
116 @synchronized(self) {
118 case PithosNodeStateFresh:
120 case PithosNodeStateRefreshNeeded:
121 freshness = PithosNodeStateRefreshing;
122 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name
132 [containerRequest setRequestUserFromDefaultTo:sharingAccount];
133 containerRequest.delegate = self;
134 containerRequest.didFinishSelector = @selector(containerRequestFinished:);
135 containerRequest.didFailSelector = @selector(containerRequestFailed:);
136 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
137 [NSNumber numberWithUnsignedInteger:10], @"retries",
140 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
141 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
143 case PithosNodeStateRefreshing:
145 case PithosNodeStateRefreshFinished:
148 children = newChildren;
151 freshness = PithosNodeStateFresh;
159 - (NSString *)displayName {
160 return [[pithosContainer.name copy] autorelease];
163 - (void)setDisplayName:(NSString *)aDisplayName {
168 if ([pithosContainer.name isEqualToString:@"pithos"])
169 icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)] retain];
170 else if ([pithosContainer.name isEqualToString:@"trash"])
171 icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)] retain];
173 icon = [sharedIcon retain];
178 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
179 if (![pithosContainer isEqualTo:aPithosContainer]) {
180 [pithosContainer release];
181 pithosContainer = [aPithosContainer retain];
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];
193 #pragma mark ASIHTTPRequestDelegate
195 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
196 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
198 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
199 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
200 [containerRequest release];
201 containerRequest = newContainerRequest;
202 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
205 NSError *error = [containerRequest error];
207 message = [NSString stringWithFormat:@"Container listing failed: %@", error];
209 message = [NSString stringWithFormat:@"Container listing failed: (%d) %@",
210 containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
211 [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
212 [newChildren release];
214 [containerRequest release];
215 containerRequest = nil;
219 @synchronized(self) {
220 freshness = PithosNodeStateRefreshNeeded;
225 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
226 NSLog(@"List container finished: %@", [containerRequest url]);
227 NSLog(@"Cached: %d", [containerRequest didUseCachedResponse]);
228 if (containerRequest.responseStatusCode == 200) {
229 if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
230 pithosContainer.blockHash = [containerRequest blockHash];
231 pithosContainer.blockSize = [containerRequest blockSize];
234 NSArray *someObjects = [containerRequest objects];
235 if (objects == nil) {
236 objects = [[NSMutableArray alloc] initWithArray:someObjects];
238 [objects addObjectsFromArray:someObjects];
240 if ([someObjects count] < 10000) {
241 if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
243 NSLog(@"using newChildren");
244 newChildren = [[NSMutableArray alloc] init];
245 NSArray *objectNames = [objects valueForKey:@"name"];
246 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
247 BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
248 for (ASIPithosObject *object in objects) {
250 [object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]]) {
251 // The check above removes false objects due to trailing slash or same prefix
253 NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
254 if ((sameNameObjectIndex == NSNotFound) ||
255 ![PithosUtilities isContentTypeDirectory:[[objects objectAtIndex:sameNameObjectIndex] contentType]]) {
256 PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
258 node.shared = shared;
259 node.sharingAccount = sharingAccount;
261 NSUInteger oldIndex = [children indexOfObject:node];
262 if (oldIndex != NSNotFound) {
263 // Use the same pointer value, if possible
264 node = [children objectAtIndex:oldIndex];
265 node.pithosContainer = pithosContainer;
266 node.pithosObject = object;
267 [keptNodes addIndex:oldIndex];
271 node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
272 [newChildren addObject:node];
274 } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
275 PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
277 node.shared = shared;
278 node.sharingAccount = sharingAccount;
280 NSUInteger oldIndex = [children indexOfObject:node];
281 if (oldIndex != NSNotFound) {
282 // Use the same pointer value, if possible
283 node = [children objectAtIndex:oldIndex];
284 node.pithosContainer = pithosContainer;
285 node.pithosObject = object;
286 [keptNodes addIndex:oldIndex];
289 [newChildren addObject:node];
291 PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
293 node.shared = shared;
294 node.sharingAccount = sharingAccount;
296 NSUInteger oldIndex = [children indexOfObject:node];
297 if (oldIndex != NSNotFound) {
298 // Use the same pointer value, if possible
299 node = [children objectAtIndex:oldIndex];
300 node.pithosContainer = pithosContainer;
301 node.pithosObject = object;
302 [keptNodes addIndex:oldIndex];
305 [newChildren addObject:node];
309 [[children objectsAtIndexes:
310 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
311 if ([keptNodes containsIndex:idx])
314 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
316 // Else cache was used and all results were fetched during this request, so existing children can be reused
317 [containerRequest release];
318 containerRequest = nil;
322 @synchronized(self) {
323 freshness = PithosNodeStateRefreshFinished;
325 // Notify observers that children are updated
326 [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
328 [containerRequest release];
329 // Do an additional request to fetch more objects
330 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name
332 marker:[[someObjects lastObject] name]
340 [containerRequest setRequestUserFromDefaultTo:sharingAccount];
341 containerRequest.delegate = self;
342 containerRequest.didFinishSelector = @selector(containerRequestFinished:);
343 containerRequest.didFailSelector = @selector(containerRequestFailed:);
344 containerRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
345 [NSNumber numberWithUnsignedInteger:10], @"retries",
348 containerRequest.downloadCache = [ASIDownloadCache sharedCache];
349 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
351 } else if (containerRequest.responseStatusCode == 304) {
352 // Container is not modified, so existing children can be reused
353 [containerRequest release];
354 containerRequest = nil;
358 @synchronized(self) {
359 freshness = PithosNodeStateRefreshFinished;
361 // Notify observers that children are updated
362 [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
364 [self containerRequestFailed:containerRequest];
368 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
369 NSLog(@"URL: %@", [request url]);
370 NSLog(@"cached: %d", [request didUseCachedResponse]);
372 if ([request isEqualTo:applyMetadataContainerRequest]) {
373 @synchronized(self) {
374 [applyMetadataContainerRequest release];
375 applyMetadataContainerRequest = nil;
378 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
379 [[pithosNodeInfoController window] makeFirstResponder:nil];
380 self.pithosContainer = [refreshMetadataContainerRequest container];
381 @synchronized(self) {
382 [refreshMetadataContainerRequest release];
383 refreshMetadataContainerRequest = nil;
388 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
389 if ([request isEqualTo:applyMetadataContainerRequest]) {
390 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
391 @synchronized(self) {
392 [applyMetadataContainerRequest release];
393 applyMetadataContainerRequest = nil;
395 } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
396 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
397 @synchronized(self) {
398 [refreshMetadataContainerRequest release];
399 refreshMetadataContainerRequest = nil;
408 @synchronized(self) {
409 if (applyMetadataContainerRequest == nil) {
410 [[pithosNodeInfoController window] makeFirstResponder:nil];
411 applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithContainerName:pithosContainer.name
412 policy:[NSDictionary dictionaryWithObjectsAndKeys:
413 policyVersioning, @"versioning",
414 [policyQuota stringValue], @"quota",
416 metadata:pithosContainer.metadata
418 applyMetadataContainerRequest.delegate = self;
419 applyMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:);
420 applyMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:);
421 [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
426 - (void)refreshInfo {
427 @synchronized(self) {
428 if (refreshMetadataContainerRequest == nil) {
429 refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithContainerName:pithosContainer.name] retain];
430 refreshMetadataContainerRequest.delegate = self;
431 refreshMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:);
432 refreshMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:);
433 refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
434 [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
442 - (void)showPithosNodeInfo:(id)sender {
443 if (!pithosNodeInfoController)
444 pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
445 [pithosNodeInfoController showWindow:sender];
446 [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
447 [NSApp activateIgnoringOtherApps:YES];