Modify nodes to make notifications configurable and provide property access to all...
[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         self.childrenUpdatedNotificationName = [NSString stringWithString:@"PithosAccountNodeChildrenUpdated"];
65     }
66     return self;
67 }
68
69 - (void)dealloc {
70     [accountRequest clearDelegatesAndCancel];
71     [accountRequest release];
72     [refreshMetadataAccountRequest clearDelegatesAndCancel];
73     [refreshMetadataAccountRequest release];
74     [applyMetadataAccountRequest clearDelegatesAndCancel];
75     [applyMetadataAccountRequest release];
76     [containers release];
77     [pithosAccount release];
78     [pithos release];
79     [super dealloc];
80 }
81
82 #pragma mark -
83 #pragma mark Properties
84
85 - (void)setPithos:(ASIPithos *)aPithos {
86     if (aPithos && ![aPithos isEqualTo:pithos]) {
87         [pithos release];
88         pithos = [aPithos retain];
89         [url release];
90         url = nil;
91     }
92 }
93
94 - (NSString *)url {
95     if (url == nil)
96         url = [[NSString alloc] initWithFormat:@"%@%@", 
97                (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL), 
98                (shared ? @"?shared" : @"")];
99     return url;
100 }
101
102 - (NSArray *)children {
103     @synchronized(self) {
104         switch (freshness) {
105             case PithosNodeStateFresh:
106                 break;
107             case PithosNodeStateRefreshNeeded:
108                 freshness = PithosNodeStateRefreshing;
109                 accountRequest = [[ASIPithosAccountRequest listContainersRequestWithPithos:pithos 
110                                                                                      limit:0 
111                                                                                     marker:nil 
112                                                                                     shared:shared 
113                                                                                      until:nil] retain];
114                 if (sharingAccount)
115                     [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
116                 accountRequest.delegate = self;
117                 accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
118                 accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
119                 accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
120                                            [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
121                                            [NSNumber numberWithUnsignedInteger:10], @"retries", 
122                                            NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", 
123                                            NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", 
124                                            nil];
125                 if (!forcedRefresh)
126                     accountRequest.downloadCache = [ASIDownloadCache sharedCache];
127                 [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
128                 break;
129             case PithosNodeStateRefreshing:
130                 break;
131             case PithosNodeStateRefreshFinished:
132                 if (newChildren) {
133                     [children release];
134                     children = newChildren;
135                     newChildren = nil;
136                 }
137                 freshness = PithosNodeStateFresh;
138             default:
139                 break;
140         }
141         return children;
142     }
143 }
144
145 - (NSString *)displayName {
146     if (displayName == nil)
147         return [NSString stringWithString:(sharingAccount ? sharingAccount : @"account")];
148     return [[displayName copy] autorelease];
149 }
150
151 - (NSImage *)icon {
152     if (icon == nil)
153         icon = [sharedIcon retain];
154     return icon;
155 }
156
157 #pragma mark -
158 #pragma mark ASIHTTPRequestDelegate
159
160 - (void)accountRequestFailed:(ASIPithosAccountRequest *)request {
161     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
162     NSUInteger retries = [[accountRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
163     if (retries > 0) {
164         ASIPithosAccountRequest *newAccountRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:accountRequest];
165         [(NSMutableDictionary *)(newAccountRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
166         [accountRequest release];
167         accountRequest = newAccountRequest;
168         [[PithosUtilities prepareRequest:accountRequest priority:[[accountRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
169     } else {
170         NSString *message;
171         NSError *error = [accountRequest error];
172         if (error)
173             message = [NSString stringWithFormat:@"Account listing failed: %@", error];
174         else
175             message = [NSString stringWithFormat:@"Account listing failed: (%d) %@", 
176                        accountRequest.responseStatusCode, accountRequest.responseStatusMessage];
177         dispatch_async(dispatch_get_main_queue(), ^{
178             [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
179         });
180         [newChildren release];
181         newChildren = nil;
182         [accountRequest release];
183         accountRequest = nil;
184         [containers release];
185         containers = nil;
186         forcedRefresh = NO;
187         @synchronized(self) {
188             freshness = PithosNodeStateRefreshNeeded;
189         }
190     }
191     [pool drain];
192 }
193
194 - (void)accountRequestFinished:(ASIPithosAccountRequest *)request {
195     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
196     NSLog(@"List account finished: %@", [accountRequest url]);
197     NSLog(@"Cached: %d", [accountRequest didUseCachedResponse]);
198     if (accountRequest.responseStatusCode == 200) {
199         self.pithosAccount = [accountRequest account];
200         
201         NSArray *someContainers = [accountRequest containers];
202         if (containers == nil) {
203             containers = [[NSMutableArray alloc] initWithArray:someContainers];
204         } else {
205             [containers addObjectsFromArray:someContainers];
206         }
207         if ([someContainers count] < 10000) {
208             if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) {
209                 // Save new children
210                 NSLog(@"using newChildren");
211                 newChildren = [[NSMutableArray alloc] init];
212                 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
213                 for (ASIPithosContainer *container in containers) {
214                     PithosContainerNode *node = [[[PithosContainerNode alloc] initWithPithos:pithos pithosContainer:container] autorelease];
215                     node.parent = self;
216                     node.shared = shared;
217                     node.sharingAccount = sharingAccount;
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             if (childrenUpdatedNotificationName) {
247                 // Notify observers that children are updated
248                 [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
249             }
250         } else {
251             [accountRequest release];
252             // Do an additional request to fetch more objects
253             accountRequest = [[ASIPithosAccountRequest listContainersRequestWithPithos:pithos 
254                                                                                  limit:0 
255                                                                                 marker:[[someContainers lastObject] name] 
256                                                                                 shared:shared 
257                                                                                  until:nil] retain];
258             if (sharingAccount)
259                 [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
260             accountRequest.delegate = self;
261             accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
262             accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
263             accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
264                                        [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority", 
265                                        [NSNumber numberWithUnsignedInteger:10], @"retries", 
266                                        NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector", 
267                                        NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector", 
268                                        nil];
269             if (!forcedRefresh)
270                 accountRequest.downloadCache = [ASIDownloadCache sharedCache];
271             [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
272         }
273     } else if (accountRequest.responseStatusCode == 304) {
274         // Account is not modified, so existing children can be reused
275         [accountRequest release];
276         accountRequest = nil;
277         [containers release];
278         containers = nil;
279         forcedRefresh = NO;
280         @synchronized(self) {
281             freshness = PithosNodeStateRefreshFinished;
282         }
283         if (childrenUpdatedNotificationName) {
284             // Notify observers that children are updated
285             [[NSNotificationCenter defaultCenter] postNotificationName:childrenUpdatedNotificationName object:self];
286         }
287     } else {
288         [self accountRequestFailed:accountRequest];
289     }
290     [pool drain];
291 }
292
293 - (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request {
294     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
295     NSLog(@"URL: %@", [request url]);
296     NSLog(@"cached: %d", [request didUseCachedResponse]);
297     
298     if ([request isEqualTo:applyMetadataAccountRequest]) {
299         @synchronized(self) {
300             [applyMetadataAccountRequest release];
301             applyMetadataAccountRequest = nil;
302         }
303         [self refreshInfo];
304     } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
305         self.pithosAccount = [refreshMetadataAccountRequest account];
306         @synchronized(self) {
307             [refreshMetadataAccountRequest release];
308             refreshMetadataAccountRequest = nil;
309         }
310     }
311     [pool drain];
312 }
313
314 - (void)accountMetadataRequestFailed:(ASIPithosAccountRequest *)request {
315     NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
316     NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
317     if (retries > 0) {
318         ASIPithosAccountRequest *newRequest = (ASIPithosAccountRequest *)[[PithosUtilities copyRequest:request] autorelease];
319         [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
320         if ([request isEqualTo:applyMetadataAccountRequest]) {
321             @synchronized(self) {
322                 [applyMetadataAccountRequest release];
323                 applyMetadataAccountRequest = newRequest;
324             }
325         } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
326             @synchronized(self) {
327                 [refreshMetadataAccountRequest release];
328                 refreshMetadataAccountRequest = newRequest;
329             }
330         }
331         [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
332     } else {
333         if ([request isEqualTo:applyMetadataAccountRequest]) {
334             dispatch_async(dispatch_get_main_queue(), ^{
335                 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataAccountRequest];
336             });
337             @synchronized(self) {
338                 [applyMetadataAccountRequest release];
339                 applyMetadataAccountRequest = nil;
340             }
341         } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
342             dispatch_async(dispatch_get_main_queue(), ^{
343                 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataAccountRequest];
344             });
345             @synchronized(self) {
346                 [refreshMetadataAccountRequest release];
347                 refreshMetadataAccountRequest = nil;
348             }
349         }
350     }
351     [pool drain];
352 }
353
354 #pragma mark -
355 #pragma mark Info
356
357 - (void)applyInfo {
358     @synchronized(self) {
359         if (applyMetadataAccountRequest == nil) {
360             NSMutableDictionary *groups = pithosAccount.groups;
361             if ([groups count] == 0)
362                 groups = [NSMutableDictionary dictionaryWithObject:@"" forKey:@"group"];
363             applyMetadataAccountRequest = [[ASIPithosAccountRequest updateAccountMetadataRequestWithPithos:pithos 
364                                                                                                     groups:groups 
365                                                                                                   metadata:pithosAccount.metadata 
366                                                                                                     update:NO] retain];
367             applyMetadataAccountRequest.delegate = self;
368             applyMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
369             applyMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
370             applyMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
371                                                     [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
372                                                     [NSNumber numberWithUnsignedInteger:10], @"retries", 
373                                                     NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", 
374                                                     NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", 
375                                                     nil];
376             [[PithosUtilities prepareRequest:applyMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
377         }
378     }
379 }
380
381 - (void)refreshInfo {
382     @synchronized(self) {
383         if (refreshMetadataAccountRequest == nil) {
384             refreshMetadataAccountRequest = [[ASIPithosAccountRequest accountMetadataRequestWithPithos:pithos] retain];
385             refreshMetadataAccountRequest.delegate = self;
386             refreshMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
387             refreshMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
388             refreshMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
389                                                       [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority", 
390                                                       [NSNumber numberWithUnsignedInteger:10], @"retries", 
391                                                       NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector", 
392                                                       NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector", 
393                                                       nil];
394             refreshMetadataAccountRequest.downloadCache = [ASIDownloadCache sharedCache];
395             [[PithosUtilities prepareRequest:refreshMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
396         }
397     }
398 }
399
400 @end