5631490c39da795215cc21c72cb62751ea5a0d07
[pithos-macos] / pithos-macos / PithosContainerNode.m
1 //
2 //  PithosContainerNode.m
3 //  pithos-macos
4 //
5 // Copyright 2011 GRNET S.A. All rights reserved.
6 //
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
9 // conditions are met:
10 // 
11 //   1. Redistributions of source code must retain the above
12 //      copyright notice, this list of conditions and the following
13 //      disclaimer.
14 // 
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.
19 // 
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.
32 // 
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.
37
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"
48
49 static NSImage *sharedIcon = nil;
50
51 @implementation PithosContainerNode
52 @synthesize pithosContainer, prefix;
53 @synthesize policyVersioning, policyQuota;
54
55 + (void)initialize {
56         if (self == [PithosContainerNode class])
57         sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain];
58 }
59
60 #pragma mark -
61 #pragma mark Object Lifecycle
62
63 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
64     if ((self = [super init])) {
65         self.pithosContainer = aPithosContainer;
66         prefix = nil;
67         self.icon = anIcon;
68         childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated";
69     }
70     return self;
71 }
72
73 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer {
74     return [self initWithPithosContainer:aPithosContainer icon:nil];
75 }
76
77 - (id)initWithContainerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
78     ASIPithosContainer *container = [ASIPithosContainer container];
79     container.name = aContainerName;
80     return [self initWithPithosContainer:container icon:anIcon];
81 }
82
83 - (id)initWithContainerName:(NSString *)aContainerName {
84     return [self initWithContainerName:aContainerName icon:nil];
85 }
86
87 - (void)dealloc {
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];
97     [prefix release];
98     [objects release];
99     [pithosContainer release];
100     [super dealloc];
101 }
102
103 #pragma mark -
104 #pragma mark Properties
105
106 - (NSString *)url {
107     if (url == nil)
108         url = [[NSString alloc] initWithFormat:@"%@/%@%@", 
109                (sharingAccount ? [ASIPithosRequest storageURLWithAuthUser:sharingAccount] : [ASIPithosRequest storageURL]), 
110                pithosContainer.name, 
111                (shared ? @"?shared" : @"")];
112     return url;
113 }
114
115 - (NSArray *)children {
116     @synchronized(self) {
117         switch (freshness) {
118             case PithosNodeStateFresh:
119                 break;
120             case PithosNodeStateRefreshNeeded:
121                 freshness = PithosNodeStateRefreshing;
122                 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name 
123                                                                                              limit:0 
124                                                                                             marker:nil 
125                                                                                             prefix:prefix 
126                                                                                          delimiter:@"/" 
127                                                                                               path:nil 
128                                                                                               meta:nil 
129                                                                                             shared:shared 
130                                                                                              until:nil] retain];
131                 if (sharingAccount)
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", 
138                                              nil];
139                 if (!forcedRefresh)
140                     containerRequest.downloadCache = [ASIDownloadCache sharedCache];
141                 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
142                 break;
143             case PithosNodeStateRefreshing:
144                 break;
145             case PithosNodeStateRefreshFinished:
146                 if (newChildren) {
147                     [children release];
148                     children = newChildren;
149                     newChildren = nil;
150                 }
151                 freshness = PithosNodeStateFresh;
152             default:
153                 break;
154         }
155         return children;
156     }
157 }
158
159 - (NSString *)displayName {
160     return [[pithosContainer.name copy] autorelease];
161 }
162
163 - (void)setDisplayName:(NSString *)aDisplayName {
164 }
165
166 - (NSImage *)icon {
167     if (icon == nil) {
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];
172         else
173             icon = [sharedIcon retain];
174     }
175     return icon;
176 }
177
178 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
179     if (![pithosContainer isEqualTo:aPithosContainer]) {
180         [pithosContainer release];
181         pithosContainer = [aPithosContainer retain];
182     }
183     if (pithosContainer.policy) {
184         self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
185         self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
186     } else {
187         self.policyVersioning = @"manual";
188         self.policyQuota = [NSNumber numberWithLongLong:0];
189     }
190 }
191
192 #pragma mark -
193 #pragma mark ASIHTTPRequestDelegate
194
195 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
196     NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
197     if (retries > 0) {
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];
203     } else {
204         NSString *message;
205         NSError *error = [containerRequest error];
206         if (error)
207             message = [NSString stringWithFormat:@"Container listing failed: %@", error];
208         else
209             message = [NSString stringWithFormat:@"Container listing failed: (%d) %@", 
210                        containerRequest.responseStatusCode, containerRequest.responseStatusMessage];
211         [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
212         [newChildren release];
213         newChildren = nil;
214         [containerRequest release];
215         containerRequest = nil;
216         [objects release];
217         objects = nil;
218         forcedRefresh = NO;
219         @synchronized(self) {
220             freshness = PithosNodeStateRefreshNeeded;
221         }
222     }
223 }
224
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];
232         }
233     
234         NSArray *someObjects = [containerRequest objects];
235         if (objects == nil) {
236             objects = [[NSMutableArray alloc] initWithArray:someObjects];
237         } else {
238             [objects addObjectsFromArray:someObjects];
239         }
240         if ([someObjects count] < 10000) {
241             if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
242                 // Save new 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) {
249                     if (!isSubdirNode || 
250                         [object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]]) {
251                         // The check above removes false objects due to trailing slash or same prefix
252                         if (object.subdir) {
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];
257                                 node.parent = self;
258                                 node.shared = shared;
259                                 node.sharingAccount = sharingAccount;
260                                 if (children) {
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];
268                                     }
269                                 }
270                                 if (sharingAccount)
271                                     node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
272                                 [newChildren addObject:node];
273                             }
274                         } else if ([PithosUtilities isContentTypeDirectory:object.contentType]) {
275                             PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
276                             node.parent = self;
277                             node.shared = shared;
278                             node.sharingAccount = sharingAccount;
279                             if (children) {
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];
287                                 }
288                             }
289                             [newChildren addObject:node];
290                         } else {
291                             PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
292                             node.parent = self;
293                             node.shared = shared;
294                             node.sharingAccount = sharingAccount;
295                             if (children) {
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];
303                                 }
304                             }
305                             [newChildren addObject:node];                                
306                         }
307                     }
308                 }
309                 [[children objectsAtIndexes:
310                   [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
311                     if ([keptNodes containsIndex:idx])
312                         return NO;
313                     return YES;
314                 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
315             }
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;
319             [objects release];
320             objects = nil;
321             forcedRefresh = NO;
322             @synchronized(self) {
323                 freshness = PithosNodeStateRefreshFinished;
324             }
325             // Notify observers that children are updated
326             [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
327         } else {
328             [containerRequest release];
329             // Do an additional request to fetch more objects
330             containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name 
331                                                                                          limit:0 
332                                                                                         marker:[[someObjects lastObject] name] 
333                                                                                         prefix:prefix 
334                                                                                      delimiter:@"/" 
335                                                                                           path:nil 
336                                                                                           meta:nil 
337                                                                                         shared:shared 
338                                                                                          until:nil] retain];
339             if (sharingAccount)
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", 
346                                          nil];
347             if (!forcedRefresh)
348             containerRequest.downloadCache = [ASIDownloadCache sharedCache];
349             [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
350         }
351     } else if (containerRequest.responseStatusCode == 304) {
352         // Container is not modified, so existing children can be reused
353         [containerRequest release];
354         containerRequest = nil;
355         [objects release];
356         objects = nil;
357         forcedRefresh = NO;
358         @synchronized(self) {
359             freshness = PithosNodeStateRefreshFinished;
360         }
361         // Notify observers that children are updated
362         [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
363     } else {
364         [self containerRequestFailed:containerRequest];
365     }
366 }
367
368 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
369     NSLog(@"URL: %@", [request url]);
370     NSLog(@"cached: %d", [request didUseCachedResponse]);
371     
372     if ([request isEqualTo:applyMetadataContainerRequest]) {
373         @synchronized(self) {
374             [applyMetadataContainerRequest release];
375             applyMetadataContainerRequest = nil;
376         }
377         [self refreshInfo];
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;
384         }
385     }
386 }
387
388 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
389     if ([request isEqualTo:applyMetadataContainerRequest]) {
390         [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
391         @synchronized(self) {
392             [applyMetadataContainerRequest release];
393             applyMetadataContainerRequest = nil;
394         }
395     } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
396         [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
397         @synchronized(self) {
398             [refreshMetadataContainerRequest release];
399             refreshMetadataContainerRequest = nil;
400         }
401     }
402 }
403
404 #pragma mark -
405 #pragma mark Info
406
407 - (void)applyInfo {
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", 
415                                                                                                                          nil] 
416                                                                                                                metadata:pithosContainer.metadata 
417                                                                                                                  update:NO] retain];
418             applyMetadataContainerRequest.delegate = self;
419             applyMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:);
420             applyMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:);
421             [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
422         }
423     }
424 }
425
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];
435         }
436     }
437 }
438
439 #pragma mark -
440 #pragma mark Actions
441
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];
448 }
449
450 @end