Reset state of root nodes when pithos is set. Send verbose messages on all node listi...
[pithos-macos] / pithos-macos / PithosAccountNode.m
1 //
2 //  PithosAccountNode.m
3 //  pithos-macos
4 //
5 // Copyright 2011-2012 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 "PithosAccountNode.h"
39 #import "PithosContainerNode.h"
40 #import "ASIPithos.h"
41 #import "ASIPithosAccountRequest.h"
42 #import "ASIPithosAccount.h"
43 #import "ASIPithosContainer.h"
44 #import "ASIDownloadCache.h"
45 #import "PithosUtilities.h"
46 #import "PithosActivityFacility.h"
47
48 static NSImage *sharedIcon = nil;
49
50 @implementation PithosAccountNode
51 @synthesize pithos, pithosAccount;
52
53 + (void)initialize {
54         if (self == [PithosAccountNode class])
55         sharedIcon = [[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)] retain];
56 }
57
58 #pragma mark -
59 #pragma mark Object Lifecycle
60
61 - (id)initWithPithos:(ASIPithos *)aPithos {
62     if ((self = [super init])) {
63         pithos = [aPithos retain];
64     }
65     return self;
66 }
67
68 - (void)dealloc {
69     [accountRequest clearDelegatesAndCancel];
70     [accountRequest release];
71     [refreshMetadataAccountRequest clearDelegatesAndCancel];
72     [refreshMetadataAccountRequest release];
73     [applyMetadataAccountRequest clearDelegatesAndCancel];
74     [applyMetadataAccountRequest release];
75     [containers release];
76     [pithosAccount release];
77     [pithos release];
78     [super dealloc];
79 }
80
81 #pragma mark -
82 #pragma mark Properties
83
84 - (void)setPithos:(ASIPithos *)aPithos {
85     if (aPithos && ![aPithos isEqualTo:pithos]) {
86         [pithos release];
87         pithos = [aPithos retain];
88         [url release];
89         url = nil;
90         [accountRequest clearDelegatesAndCancel];
91         [accountRequest release];
92         accountRequest = nil;
93         [refreshMetadataAccountRequest clearDelegatesAndCancel];
94         [refreshMetadataAccountRequest release];
95         refreshMetadataAccountRequest = nil;
96         [applyMetadataAccountRequest clearDelegatesAndCancel];
97         [applyMetadataAccountRequest release];
98         applyMetadataAccountRequest = nil;
99         reset = YES;
100     }
101 }
102
103 - (NSString *)url {
104     if (url == nil)
105         url = [[NSString alloc] initWithFormat:@"%@%@", 
106                (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
107                (shared ? @"?shared" : @"")];
108     return url;
109 }
110
111 - (NSArray *)children {
112     @synchronized(self) {
113         if (reset) {
114             [accountRequest clearDelegatesAndCancel];
115             [accountRequest release];
116             accountRequest = nil;
117             [refreshMetadataAccountRequest clearDelegatesAndCancel];
118             [refreshMetadataAccountRequest release];
119             refreshMetadataAccountRequest = nil;
120             [applyMetadataAccountRequest clearDelegatesAndCancel];
121             [applyMetadataAccountRequest release];
122             applyMetadataAccountRequest = nil;
123             [children release];
124             children = nil;
125             [newChildren release];
126             newChildren = nil;
127             self.pithosAccount = nil;
128             freshness = PithosNodeStateRefreshNeeded;
129             forcedRefresh = YES;
130             reset = NO;
131             [self postChildrenUpdatedNotificationName];
132         }
133         switch (freshness) {
134             case PithosNodeStateFresh:
135                 break;
136             case PithosNodeStateRefreshNeeded:
137                 freshness = PithosNodeStateRefreshing;
138                 accountRequest = [[ASIPithosAccountRequest listContainersRequestWithPithos:pithos 
139                                                                                      limit:0 
140                                                                                     marker:nil 
141                                                                                     shared:shared 
142                                                                                      until:nil] retain];
143                 if (sharingAccount)
144                     [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
145                 else if (!forcedRefresh)
146                     accountRequest.downloadCache = [ASIDownloadCache sharedCache];
147                 accountRequest.delegate = self;
148                 accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
149                 accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
150                 accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
151                                            [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
152                                            [NSNumber numberWithUnsignedInteger:10], @"retries", 
153                                            NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", 
154                                            NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", 
155                                            nil];
156                 [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
157                 break;
158             case PithosNodeStateRefreshing:
159                 break;
160             case PithosNodeStateRefreshFinished:
161                 if (newChildren) {
162                     [children release];
163                     children = newChildren;
164                     newChildren = nil;
165                 }
166                 freshness = PithosNodeStateFresh;
167             default:
168                 break;
169         }
170         return children;
171     }
172 }
173
174 - (NSString *)displayName {
175     if (displayName == nil)
176         return [NSString stringWithString:(sharingAccount ? sharingAccount : @"account")];
177     return [[displayName copy] autorelease];
178 }
179
180 - (NSImage *)icon {
181     if (icon == nil)
182         icon = [sharedIcon retain];
183     return icon;
184 }
185
186 #pragma mark -
187 #pragma mark ASIHTTPRequestDelegate
188
189 - (void)accountRequestFailed:(ASIPithosAccountRequest *)request {
190     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
191     NSString *message;
192     NSError *error = [accountRequest error];
193     if (error)
194         message = [NSString stringWithFormat:@"Account listing %@ failed: %@", accountRequest.url, [error localizedDescription]];
195     else
196         message = [NSString stringWithFormat:@"Account listing %@ failed: (%d) %@", 
197                    accountRequest.url, accountRequest.responseStatusCode, accountRequest.responseStatusMessage];
198     dispatch_async(dispatch_get_main_queue(), ^{
199         [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
200     });
201     NSUInteger retries = [[accountRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
202     if (retries > 0) {
203         ASIPithosAccountRequest *newAccountRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:accountRequest];
204         [(NSMutableDictionary *)(newAccountRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
205         [accountRequest release];
206         accountRequest = newAccountRequest;
207         [[PithosUtilities prepareRequest:accountRequest priority:[[accountRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
208     } else {
209         [newChildren release];
210         newChildren = nil;
211         [accountRequest release];
212         accountRequest = nil;
213         [containers release];
214         containers = nil;
215         forcedRefresh = NO;
216         @synchronized(self) {
217             freshness = PithosNodeStateRefreshNeeded;
218         }
219     }
220     [pool drain];
221 }
222
223 - (void)accountRequestFinished:(ASIPithosAccountRequest *)request {
224     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
225     NSLog(@"List account finished: %@", [accountRequest url]);
226     NSLog(@"Cached: %d", [accountRequest didUseCachedResponse]);
227     if (accountRequest.responseStatusCode == 200) {
228         self.pithosAccount = [accountRequest account];
229         
230         NSArray *someContainers = [accountRequest containers];
231         if (containers == nil) {
232             containers = [[NSMutableArray alloc] initWithArray:someContainers];
233         } else {
234             [containers addObjectsFromArray:someContainers];
235         }
236         if ([someContainers count] < 10000) {
237             if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) {
238                 // Save new children
239                 NSLog(@"using newChildren");
240                 newChildren = [[NSMutableArray alloc] init];
241                 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
242                 for (ASIPithosContainer *container in containers) {
243                     PithosContainerNode *node = [[[PithosContainerNode alloc] initWithPithos:pithos pithosContainer:container] autorelease];
244                     node.parent = self;
245                     node.shared = shared;
246                     node.sharingAccount = sharingAccount;
247                     node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
248                     if (children) {
249                         NSUInteger oldIndex = [children indexOfObject:node];
250                         if (oldIndex != NSNotFound) {
251                             // Use the same pointer value, if possible
252                             node = [children objectAtIndex:oldIndex];
253 //                            node.pithosContainer = container;
254                             [node setLimitedPithosContainer:container];
255                             [keptNodes addIndex:oldIndex];
256                         }
257                     }
258                     [newChildren addObject:node];
259                 }
260                 [[children objectsAtIndexes:
261                   [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
262                     if ([keptNodes containsIndex:idx])
263                         return NO;
264                     return YES;
265                 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
266             }
267             // Else cache was used and all results were fetched during this request, so existing children can be reused
268             [accountRequest release];
269             accountRequest = nil;
270             [containers release];
271             containers = nil;
272             forcedRefresh = NO;
273             @synchronized(self) {
274                 freshness = PithosNodeStateRefreshFinished;
275             }
276             [self postChildrenUpdatedNotificationName];
277         } else {
278             [accountRequest release];
279             // Do an additional request to fetch more objects
280             accountRequest = [[ASIPithosAccountRequest listContainersRequestWithPithos:pithos 
281                                                                                  limit:0 
282                                                                                 marker:[[someContainers lastObject] name] 
283                                                                                 shared:shared 
284                                                                                  until:nil] retain];
285             if (sharingAccount)
286                 [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
287             else if (!forcedRefresh)
288                 accountRequest.downloadCache = [ASIDownloadCache sharedCache];
289             accountRequest.delegate = self;
290             accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
291             accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
292             accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
293                                        [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
294                                        [NSNumber numberWithUnsignedInteger:10], @"retries", 
295                                        NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", 
296                                        NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", 
297                                        nil];
298             [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
299         }
300     } else if (accountRequest.responseStatusCode == 304) {
301         // Account is not modified, so existing children can be reused
302         [accountRequest release];
303         accountRequest = nil;
304         [containers release];
305         containers = nil;
306         forcedRefresh = NO;
307         @synchronized(self) {
308             freshness = PithosNodeStateRefreshFinished;
309         }
310         [self postChildrenUpdatedNotificationName];
311     } else {
312         [self accountRequestFailed:accountRequest];
313     }
314     [pool drain];
315 }
316
317 - (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request {
318     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
319     NSLog(@"URL: %@", [request url]);
320     NSLog(@"cached: %d", [request didUseCachedResponse]);
321     
322     if ([request isEqualTo:applyMetadataAccountRequest]) {
323         @synchronized(self) {
324             [applyMetadataAccountRequest release];
325             applyMetadataAccountRequest = nil;
326         }
327         [self refreshInfo];
328     } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
329         self.pithosAccount = [refreshMetadataAccountRequest account];
330         @synchronized(self) {
331             [refreshMetadataAccountRequest release];
332             refreshMetadataAccountRequest = nil;
333         }
334     }
335     [pool drain];
336 }
337
338 - (void)accountMetadataRequestFailed:(ASIPithosAccountRequest *)request {
339     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
340     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
341     if (retries > 0) {
342         ASIPithosAccountRequest *newRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:request];
343         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
344         if ([request isEqualTo:applyMetadataAccountRequest]) {
345             @synchronized(self) {
346                 [applyMetadataAccountRequest release];
347                 applyMetadataAccountRequest = newRequest;
348             }
349         } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
350             @synchronized(self) {
351                 [refreshMetadataAccountRequest release];
352                 refreshMetadataAccountRequest = newRequest;
353             }
354         }
355         [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
356     } else {
357         if ([request isEqualTo:applyMetadataAccountRequest]) {
358             [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataAccountRequest];
359             @synchronized(self) {
360                 [applyMetadataAccountRequest release];
361                 applyMetadataAccountRequest = nil;
362             }
363         } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
364             [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataAccountRequest];
365             @synchronized(self) {
366                 [refreshMetadataAccountRequest release];
367                 refreshMetadataAccountRequest = nil;
368             }
369         }
370     }
371     [pool drain];
372 }
373
374 #pragma mark -
375 #pragma mark Info
376
377 - (void)applyInfo {
378     @synchronized(self) {
379         if (applyMetadataAccountRequest == nil) {
380             NSMutableDictionary *groups = pithosAccount.groups;
381             if ([groups count] == 0)
382                 groups = [NSMutableDictionary dictionaryWithObject:@"" forKey:@"group"];
383             applyMetadataAccountRequest = [[ASIPithosAccountRequest updateAccountMetadataRequestWithPithos:pithos 
384                                                                                                     groups:groups 
385                                                                                                   metadata:pithosAccount.metadata 
386                                                                                                     update:NO] retain];
387             applyMetadataAccountRequest.delegate = self;
388             applyMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
389             applyMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
390             applyMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
391                                                     [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
392                                                     [NSNumber numberWithUnsignedInteger:10], @"retries", 
393                                                     NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", 
394                                                     NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", 
395                                                     nil];
396             [[PithosUtilities prepareRequest:applyMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
397         }
398     }
399 }
400
401 - (void)refreshInfo {
402     @synchronized(self) {
403         if (refreshMetadataAccountRequest == nil) {
404             refreshMetadataAccountRequest = [[ASIPithosAccountRequest accountMetadataRequestWithPithos:pithos] retain];
405             refreshMetadataAccountRequest.delegate = self;
406             refreshMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
407             refreshMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
408             refreshMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
409                                                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
410                                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
411                                                       NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", 
412                                                       NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", 
413                                                       nil];
414             if (!sharingAccount)
415                 refreshMetadataAccountRequest.downloadCache = [ASIDownloadCache sharedCache];
416             [[PithosUtilities prepareRequest:refreshMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
417         }
418     }
419 }
420
421 @end