All requests made asynchronous.
[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
48 static NSImage *sharedIcon = nil;
49
50 @implementation PithosContainerNode
51 @synthesize pithosContainer, prefix;
52 @synthesize policyVersioning, policyQuota;
53
54 + (void)initialize {
55         if (self == [PithosContainerNode class])
56         sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGenericHardDiskIcon)] retain];
57 }
58
59 #pragma mark -
60 #pragma mark Object Lifecycle
61
62 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer icon:(NSImage *)anIcon {
63     if ((self = [super init])) {
64         self.pithosContainer = aPithosContainer;
65         prefix = nil;
66         self.icon = anIcon;
67         childrenUpdatedNotificationName = @"PithosContainerNodeChildrenUpdated";
68     }
69     return self;
70 }
71
72 - (id)initWithPithosContainer:(ASIPithosContainer *)aPithosContainer {
73     return [self initWithPithosContainer:aPithosContainer icon:nil];
74 }
75
76 - (id)initWithContainerName:(NSString *)aContainerName icon:(NSImage *)anIcon {
77     ASIPithosContainer *container = [ASIPithosContainer container];
78     container.name = aContainerName;
79     return [self initWithPithosContainer:container icon:anIcon];
80 }
81
82 - (id)initWithContainerName:(NSString *)aContainerName {
83     return [self initWithContainerName:aContainerName icon:nil];
84 }
85
86 - (void)dealloc {
87     [containerRequest clearDelegatesAndCancel];
88     [containerRequest release];
89     [refreshMetadataContainerRequest clearDelegatesAndCancel];
90     [refreshMetadataContainerRequest release];
91     [applyMetadataContainerRequest clearDelegatesAndCancel];
92     [applyMetadataContainerRequest release];
93     [policyQuota release];
94     [policyVersioning release];
95     [childrenUpdatedNotificationName release];
96     [prefix release];
97     [objects release];
98     [pithosContainer release];
99     [super dealloc];
100 }
101
102 #pragma mark -
103 #pragma mark Properties
104
105 - (NSString *)url {
106     if (url == nil)
107         url = [[NSString alloc] initWithFormat:@"%@/%@%@", 
108                (sharingAccount ? [ASIPithosRequest storageURLWithAuthUser:sharingAccount] : [ASIPithosRequest storageURL]), 
109                pithosContainer.name, 
110                (shared ? @"?shared" : @"")];
111     return url;
112 }
113
114 - (NSArray *)children {
115     @synchronized(self) {
116         switch (freshness) {
117             case PithosNodeStateFresh:
118                 break;
119             case PithosNodeStateRefreshNeeded:
120                 freshness = PithosNodeStateRefreshing;
121                 containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name 
122                                                                                              limit:0 
123                                                                                             marker:nil 
124                                                                                             prefix:prefix 
125                                                                                          delimiter:@"/" 
126                                                                                               path:nil 
127                                                                                               meta:nil 
128                                                                                             shared:shared 
129                                                                                              until:nil] retain];
130                 if (sharingAccount)
131                     [containerRequest setRequestUserFromDefaultTo:sharingAccount];
132                 containerRequest.delegate = self;
133                 containerRequest.didFinishSelector = @selector(containerRequestFinished:);
134                 containerRequest.didFailSelector = @selector(containerRequestFailed:);
135                 if (!forcedRefresh)
136                     containerRequest.downloadCache = [ASIDownloadCache sharedCache];
137                 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
138                 break;
139             case PithosNodeStateRefreshing:
140                 break;
141             case PithosNodeStateRefreshFinished:
142                 if (newChildren) {
143                     [children release];
144                     children = newChildren;
145                     newChildren = nil;
146                 }
147                 freshness = PithosNodeStateFresh;
148             default:
149                 break;
150         }
151         return children;
152     }
153 }
154
155 - (NSString *)displayName {
156     return [[pithosContainer.name copy] autorelease];
157 }
158
159 - (void)setDisplayName:(NSString *)aDisplayName {
160 }
161
162 - (NSImage *)icon {
163     if (icon == nil) {
164         if ([pithosContainer.name isEqualToString:@"pithos"])
165             icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)] retain];
166         else if ([pithosContainer.name isEqualToString:@"trash"])
167             icon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)] retain];
168         else
169             icon = [sharedIcon retain];
170     }
171     return icon;
172 }
173
174 - (void)setPithosContainer:(ASIPithosContainer *)aPithosContainer {
175     if (![pithosContainer isEqualTo:aPithosContainer]) {
176         [pithosContainer release];
177         pithosContainer = [aPithosContainer retain];
178     }
179     if (pithosContainer.policy) {
180         self.policyVersioning = [pithosContainer.policy objectForKey:@"versioning"];
181         self.policyQuota = [NSNumber numberWithLongLong:[[pithosContainer.policy objectForKey:@"quota"] longLongValue]];
182     } else {
183         self.policyVersioning = @"manual";
184         self.policyQuota = [NSNumber numberWithLongLong:0];
185     }
186 }
187
188 #pragma mark -
189 #pragma mark ASIHTTPRequestDelegate
190
191 - (void)containerRequestFinished:(ASIPithosContainerRequest *)request {
192     NSLog(@"URL: %@", [containerRequest url]);
193     NSLog(@"cached: %d", [containerRequest didUseCachedResponse]);
194     
195     if ((pithosContainer.blockHash == nil) || (pithosContainer.blockSize == 0)) {
196         pithosContainer.blockHash = [containerRequest blockHash];
197         pithosContainer.blockSize = [containerRequest blockSize];
198     }
199     
200     NSArray *someObjects = [containerRequest objects];
201     if (objects == nil) {
202         objects = [[NSMutableArray alloc] initWithArray:someObjects];
203     } else {
204         [objects addObjectsFromArray:someObjects];
205     }
206     if ([someObjects count] < 10000) {
207         if (!containerRequest.didUseCachedResponse || ([objects count] != [someObjects count]) || !children) {
208             // Save new children
209             NSLog(@"using newChildren");
210             newChildren = [[NSMutableArray alloc] init];
211             NSArray *objectNames = [objects valueForKey:@"name"];
212             NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
213             BOOL isSubdirNode = ([self class] == [PithosSubdirNode class]);
214             for (ASIPithosObject *object in objects) {
215                 if (!isSubdirNode || 
216                     [object.name hasPrefix:[((PithosSubdirNode *)self).prefix stringByAppendingString:@"/"]]) {
217                     // The check above removes false objects due to trailing slash or same prefix
218                     if (object.subdir) {
219                         NSUInteger sameNameObjectIndex = [objectNames indexOfObject:[object.name substringToIndex:([object.name length] - 1)]];
220                         if ((sameNameObjectIndex == NSNotFound) ||
221                             ![[[objects objectAtIndex:sameNameObjectIndex] contentType] isEqualToString:@"application/directory"]) {
222                             PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
223                             node.parent = self;
224                             node.shared = shared;
225                             node.sharingAccount = sharingAccount;
226                             if (children) {
227                                 NSUInteger oldIndex = [children indexOfObject:node];
228                                 if (oldIndex != NSNotFound) {
229                                     // Use the same pointer value, if possible
230                                     node = [children objectAtIndex:oldIndex];
231                                     node.pithosContainer = pithosContainer;
232                                     node.pithosObject = object;
233                                     [keptNodes addIndex:oldIndex];
234                                 }
235                             }
236                             if (sharingAccount)
237                                 node.pithosObject.allowedTo = [NSString stringWithString:@"read"];
238                             [newChildren addObject:node];
239                         }
240                     } else if ([object.contentType isEqualToString:@"application/directory"]) {
241                         PithosSubdirNode *node = [[[PithosSubdirNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
242                         node.parent = self;
243                         node.shared = shared;
244                         node.sharingAccount = sharingAccount;
245                         if (children) {
246                             NSUInteger oldIndex = [children indexOfObject:node];
247                             if (oldIndex != NSNotFound) {
248                                 // Use the same pointer value, if possible
249                                 node = [children objectAtIndex:oldIndex];
250                                 node.pithosContainer = pithosContainer;
251                                 node.pithosObject = object;
252                                 [keptNodes addIndex:oldIndex];
253                             }
254                         }
255                         [newChildren addObject:node];
256                     } else {
257                         PithosObjectNode *node = [[[PithosObjectNode alloc] initWithPithosContainer:pithosContainer pithosObject:object] autorelease];
258                         node.parent = self;
259                         node.shared = shared;
260                         node.sharingAccount = sharingAccount;
261                         if (children) {
262                             NSUInteger oldIndex = [children indexOfObject:node];
263                             if (oldIndex != NSNotFound) {
264                                 // Use the same pointer value, if possible
265                                 node = [children objectAtIndex:oldIndex];
266                                 node.pithosContainer = pithosContainer;
267                                 node.pithosObject = object;
268                                 [keptNodes addIndex:oldIndex];
269                             }
270                         }
271                         [newChildren addObject:node];                                
272                     }
273                 }
274             }
275             [[children objectsAtIndexes:
276               [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
277                 if ([keptNodes containsIndex:idx])
278                     return NO;
279                 return YES;
280             }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
281         }
282         // Else cache was used and all results were fetched during this request, so existing children can be reused
283         [containerRequest release];
284         containerRequest = nil;
285         [objects release];
286         objects = nil;
287         forcedRefresh = NO;
288         @synchronized(self) {
289             freshness = PithosNodeStateRefreshFinished;
290         }
291         // Notify observers that children are updated
292         [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
293     } else {
294         [containerRequest release];
295         // Do an additional request to fetch more objects
296         containerRequest = [[ASIPithosContainerRequest listObjectsRequestWithContainerName:pithosContainer.name 
297                                                                                      limit:0 
298                                                                                     marker:[[someObjects lastObject] name] 
299                                                                                     prefix:prefix 
300                                                                                  delimiter:@"/" 
301                                                                                       path:nil 
302                                                                                       meta:nil 
303                                                                                     shared:shared 
304                                                                                      until:nil] retain];
305         if (sharingAccount)
306             [containerRequest setRequestUserFromDefaultTo:sharingAccount];
307         containerRequest.delegate = self;
308         if (!forcedRefresh)
309         containerRequest.downloadCache = [ASIDownloadCache sharedCache];
310         [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
311     }
312 }
313
314 - (void)containerRequestFailed:(ASIPithosContainerRequest *)request {
315     [PithosUtilities httpRequestErrorAlertWithRequest:request];
316     [newChildren release];
317     newChildren = nil;
318     [containerRequest release];
319     containerRequest = nil;
320     [objects release];
321     objects = nil;
322     forcedRefresh = NO;
323     @synchronized(self) {
324         freshness = PithosNodeStateRefreshNeeded;
325     }
326 }
327
328 - (void)containerMetadataRequestFinished:(ASIPithosContainerRequest *)request {
329     NSLog(@"URL: %@", [request url]);
330     NSLog(@"cached: %d", [request didUseCachedResponse]);
331     
332     if ([request isEqualTo:applyMetadataContainerRequest]) {
333         @synchronized(self) {
334             [applyMetadataContainerRequest release];
335             applyMetadataContainerRequest = nil;
336         }
337         [self refreshInfo];
338     } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
339         [[pithosNodeInfoController window] makeFirstResponder:nil];
340         self.pithosContainer = [refreshMetadataContainerRequest container];
341         @synchronized(self) {
342             [refreshMetadataContainerRequest release];
343             refreshMetadataContainerRequest = nil;
344         }
345     }
346 }
347
348 - (void)containerMetadataRequestFailed:(ASIPithosContainerRequest *)request {
349     if ([request isEqualTo:applyMetadataContainerRequest]) {
350         [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataContainerRequest];
351         @synchronized(self) {
352             [applyMetadataContainerRequest release];
353             applyMetadataContainerRequest = nil;
354         }
355     } else if ([request isEqualTo:refreshMetadataContainerRequest]) {
356         [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataContainerRequest];
357         @synchronized(self) {
358             [refreshMetadataContainerRequest release];
359             refreshMetadataContainerRequest = nil;
360         }
361     }
362 }
363
364 #pragma mark -
365 #pragma mark Info
366
367 - (void)applyInfo {
368     @synchronized(self) {
369         if (applyMetadataContainerRequest == nil) {
370             [[pithosNodeInfoController window] makeFirstResponder:nil];
371             applyMetadataContainerRequest = [[ASIPithosContainerRequest updateContainerMetadataRequestWithContainerName:pithosContainer.name 
372                                                                                                                  policy:[NSDictionary dictionaryWithObjectsAndKeys:
373                                                                                                                          policyVersioning, @"versioning", 
374                                                                                                                          [policyQuota stringValue], @"quota", 
375                                                                                                                          nil] 
376                                                                                                                metadata:pithosContainer.metadata 
377                                                                                                                  update:NO] retain];
378             applyMetadataContainerRequest.delegate = self;
379             applyMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:);
380             applyMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:);
381             [[PithosUtilities prepareRequest:applyMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
382         }
383     }
384 }
385
386 - (void)refreshInfo {
387     @synchronized(self) {
388         if (refreshMetadataContainerRequest == nil) {
389             refreshMetadataContainerRequest = [[ASIPithosContainerRequest containerMetadataRequestWithContainerName:pithosContainer.name] retain];
390             refreshMetadataContainerRequest.delegate = self;
391             refreshMetadataContainerRequest.didFinishSelector = @selector(containerMetadataRequestFinished:);
392             refreshMetadataContainerRequest.didFailSelector = @selector(containerMetadataRequestFailed:);
393             refreshMetadataContainerRequest.downloadCache = [ASIDownloadCache sharedCache];
394             [[PithosUtilities prepareRequest:refreshMetadataContainerRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
395         }
396     }
397 }
398
399 #pragma mark -
400 #pragma mark Actions
401
402 - (void)showPithosNodeInfo:(id)sender {
403     if (!pithosNodeInfoController)
404         pithosNodeInfoController = [[PithosContainerNodeInfoController alloc] initWithPithosNode:self];
405     [pithosNodeInfoController showWindow:sender];
406     [[pithosNodeInfoController window] makeKeyAndOrderFront:sender];
407     [NSApp activateIgnoringOtherApps:YES];
408 }
409
410 @end