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