4c3a9ce643d08de38fbb2fabe28762616b201020
[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     @autoreleasepool {
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     }
221 }
222
223 - (void)accountRequestFinished:(ASIPithosAccountRequest *)request {
224     @autoreleasepool {
225         DLog(@"List account finished: %@", [accountRequest url]);
226         DLog(@"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                     DLog(@"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     }
315 }
316
317 - (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request {
318     @autoreleasepool {
319         DLog(@"URL: %@", [request url]);
320         DLog(@"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     }
336 }
337
338 - (void)accountMetadataRequestFailed:(ASIPithosAccountRequest *)request {
339     @autoreleasepool {
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     }
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