5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
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.
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.
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.
38 #import "PithosAccountNode.h"
39 #import "PithosContainerNode.h"
41 #import "ASIPithosAccountRequest.h"
42 #import "ASIPithosAccount.h"
43 #import "ASIPithosContainer.h"
44 #import "ASIDownloadCache.h"
45 #import "PithosAccount.h"
46 #import "PithosUtilities.h"
47 #import "PithosActivityFacility.h"
49 static NSImage *sharedIcon = nil;
51 @implementation PithosAccountNode
52 @synthesize pithos, pithosAccount, translatedGroups;
55 if (self == [PithosAccountNode class])
56 sharedIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
60 #pragma mark Object Lifecycle
62 - (id)initWithPithos:(ASIPithos *)aPithos {
63 if ((self = [super init])) {
70 [accountRequest clearDelegatesAndCancel];
71 [refreshMetadataAccountRequest clearDelegatesAndCancel];
72 [applyMetadataAccountRequest clearDelegatesAndCancel];
76 #pragma mark Properties
78 - (void)setPithos:(ASIPithos *)aPithos {
79 if (aPithos && ![aPithos isEqualTo:pithos]) {
82 [accountRequest clearDelegatesAndCancel];
84 [refreshMetadataAccountRequest clearDelegatesAndCancel];
85 refreshMetadataAccountRequest = nil;
86 [applyMetadataAccountRequest clearDelegatesAndCancel];
87 applyMetadataAccountRequest = nil;
92 - (void)setPithosAccount:(ASIPithosAccount *)aPithosAccount {
93 if (!aPithosAccount) {
94 pithosAccount = aPithosAccount;
95 self.translatedGroups = [NSMutableDictionary dictionary];
96 } else if (![aPithosAccount isEqualTo:pithosAccount]) {
97 pithosAccount = aPithosAccount;
98 if (pithosAccountManager) {
99 NSMutableArray *UUIDs = [NSMutableArray array];
100 for (NSString *groupName in pithosAccount.groups) {
101 [UUIDs addObjectsFromArray:[pithosAccount.groups objectForKey:groupName]];
104 [pithosAccountManager updateUserCatalogForForDisplaynames:nil UUIDs:UUIDs];
107 NSMutableDictionary *newTranslatedGroups = [NSMutableDictionary dictionaryWithCapacity:pithosAccount.groups.count];
108 for (NSString *groupName in pithosAccount.groups) {
109 NSMutableArray *groupUsers = [NSMutableArray array];
110 for (NSString *UUID in [pithosAccount.groups objectForKey:groupName]) {
111 [groupUsers addObject:[pithosAccountManager displaynameForUUID:UUID safe:YES]];
113 [newTranslatedGroups setObject:groupUsers forKey:groupName];
115 self.translatedGroups = newTranslatedGroups;
117 self.translatedGroups = [pithosAccount.groups copy];
124 url = [[NSString alloc] initWithFormat:@"%@%@",
125 (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL),
126 (shared ? @"?shared" : @"")];
130 - (NSArray *)children {
131 @synchronized(self) {
133 [accountRequest clearDelegatesAndCancel];
134 accountRequest = nil;
135 [refreshMetadataAccountRequest clearDelegatesAndCancel];
136 refreshMetadataAccountRequest = nil;
137 [applyMetadataAccountRequest clearDelegatesAndCancel];
138 applyMetadataAccountRequest = nil;
141 self.pithosAccount = nil;
142 freshness = PithosNodeStateRefreshNeeded;
145 [self postChildrenUpdatedNotificationName];
148 case PithosNodeStateFresh:
150 case PithosNodeStateRefreshNeeded:
151 freshness = PithosNodeStateRefreshing;
152 accountRequest = [ASIPithosAccountRequest listContainersRequestWithPithos:pithos
158 [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
159 else if (!forcedRefresh)
160 accountRequest.downloadCache = [ASIDownloadCache sharedCache];
161 accountRequest.delegate = self;
162 accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
163 accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
164 accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
165 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
166 [NSNumber numberWithUnsignedInteger:10], @"retries",
167 NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector",
168 NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector",
170 [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
172 case PithosNodeStateRefreshing:
174 case PithosNodeStateRefreshFinished:
176 children = newChildren;
179 freshness = PithosNodeStateFresh;
187 - (NSString *)displayName {
188 if (displayName == nil) {
189 if (!sharingAccount) {
191 } else if (pithosAccountManager) {
192 return [pithosAccountManager displaynameForUUID:sharingAccount safe:YES];
194 return [sharingAccount copy];
197 return [displayName copy];
207 #pragma mark ASIHTTPRequestDelegate
209 - (void)accountRequestFailed:(ASIPithosAccountRequest *)request {
212 NSError *error = [accountRequest error];
214 message = [NSString stringWithFormat:@"Account listing %@ failed: %@", accountRequest.url, [error localizedDescription]];
216 message = [NSString stringWithFormat:@"Account listing %@ failed: (%d) %@",
217 accountRequest.url, accountRequest.responseStatusCode, accountRequest.responseStatusMessage];
218 dispatch_async(dispatch_get_main_queue(), ^{
219 [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
221 NSUInteger retries = [[accountRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
223 ASIPithosAccountRequest *newAccountRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:accountRequest];
224 [(NSMutableDictionary *)(newAccountRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
225 accountRequest = newAccountRequest;
226 [[PithosUtilities prepareRequest:accountRequest priority:[[accountRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
229 accountRequest = nil;
232 @synchronized(self) {
233 freshness = PithosNodeStateRefreshNeeded;
239 - (void)accountRequestFinished:(ASIPithosAccountRequest *)request {
241 DLog(@"List account finished: %@", [accountRequest url]);
242 DLog(@"Cached: %d", [accountRequest didUseCachedResponse]);
243 if (accountRequest.responseStatusCode == 200) {
244 self.pithosAccount = [accountRequest account];
246 NSArray *someContainers = [accountRequest containers];
247 if (containers == nil) {
248 containers = [[NSMutableArray alloc] initWithArray:someContainers];
250 [containers addObjectsFromArray:someContainers];
252 if ([someContainers count] < 10000) {
253 if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) {
255 DLog(@"using newChildren");
256 newChildren = [[NSMutableArray alloc] init];
257 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
258 for (ASIPithosContainer *container in containers) {
259 PithosContainerNode *node = [[PithosContainerNode alloc] initWithPithos:pithos pithosContainer:container];
261 node.shared = shared;
262 node.sharingAccount = sharingAccount;
263 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
264 node.pithosAccountManager = pithosAccountManager;
266 NSUInteger oldIndex = [children indexOfObject:node];
267 if (oldIndex != NSNotFound) {
268 // Use the same pointer value, if possible
269 node = [children objectAtIndex:oldIndex];
270 // node.pithosContainer = container;
271 [node setLimitedPithosContainer:container];
272 [keptNodes addIndex:oldIndex];
275 [newChildren addObject:node];
277 [[children objectsAtIndexes:
278 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
279 if ([keptNodes containsIndex:idx])
282 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
284 // Else cache was used and all results were fetched during this request, so existing children can be reused
285 accountRequest = nil;
288 @synchronized(self) {
289 freshness = PithosNodeStateRefreshFinished;
291 [self postChildrenUpdatedNotificationName];
293 // Do an additional request to fetch more objects
294 accountRequest = [ASIPithosAccountRequest listContainersRequestWithPithos:pithos
296 marker:[[someContainers lastObject] name]
300 [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
301 else if (!forcedRefresh)
302 accountRequest.downloadCache = [ASIDownloadCache sharedCache];
303 accountRequest.delegate = self;
304 accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
305 accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
306 accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
307 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
308 [NSNumber numberWithUnsignedInteger:10], @"retries",
309 NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector",
310 NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector",
312 [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
314 } else if (accountRequest.responseStatusCode == 304) {
315 // Account is not modified, so existing children can be reused
316 accountRequest = nil;
319 @synchronized(self) {
320 freshness = PithosNodeStateRefreshFinished;
322 [self postChildrenUpdatedNotificationName];
324 [self accountRequestFailed:accountRequest];
329 - (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request {
331 DLog(@"URL: %@", [request url]);
332 DLog(@"cached: %d", [request didUseCachedResponse]);
334 if ([request isEqualTo:applyMetadataAccountRequest]) {
335 @synchronized(self) {
336 applyMetadataAccountRequest = nil;
339 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
340 self.pithosAccount = [refreshMetadataAccountRequest account];
341 @synchronized(self) {
342 refreshMetadataAccountRequest = nil;
348 - (void)accountMetadataRequestFailed:(ASIPithosAccountRequest *)request {
350 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
352 ASIPithosAccountRequest *newRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:request];
353 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
354 if ([request isEqualTo:applyMetadataAccountRequest]) {
355 @synchronized(self) {
356 applyMetadataAccountRequest = newRequest;
358 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
359 @synchronized(self) {
360 refreshMetadataAccountRequest = newRequest;
363 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
365 if ([request isEqualTo:applyMetadataAccountRequest]) {
366 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataAccountRequest];
367 @synchronized(self) {
368 applyMetadataAccountRequest = nil;
370 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
371 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataAccountRequest];
372 @synchronized(self) {
373 refreshMetadataAccountRequest = nil;
384 @synchronized(self) {
385 if (applyMetadataAccountRequest == nil) {
386 NSMutableDictionary *groups = [NSMutableDictionary dictionary];
387 if (translatedGroups.count) {
388 for (NSString *groupName in translatedGroups) {
389 if (!groupName.length ||
390 [groupName rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound) {
391 NSAlert *alert = [[NSAlert alloc] init];
392 [alert setMessageText:@"Invalid Input"];
393 [alert setInformativeText:@"Group names cannot be empty or contain ' ', '-', '_', '~', ',' or ';'."];
394 [alert addButtonWithTitle:@"OK"];
399 if (pithosAccountManager) {
400 NSMutableSet *allGroupUsers = [NSMutableSet set];
401 for (NSMutableArray *groupUsers in [translatedGroups objectEnumerator]) {
402 [allGroupUsers addObjectsFromArray:groupUsers];
404 [allGroupUsers removeObject:@""];
405 [allGroupUsers removeObject:@"*"];
406 if (allGroupUsers.count) {
407 ASIPithosRequest *userCatalogRequest = [pithosAccountManager updateUserCatalogForForDisplaynames:[allGroupUsers allObjects]
409 if (userCatalogRequest.error || ((userCatalogRequest.responseStatusCode != 200) && (userCatalogRequest.responseStatusCode != 404))) {
411 } else if (userCatalogRequest.responseStatusCode == 200) {
412 // Check if all users exist.
413 NSDictionary *displaynameCatalog = [userCatalogRequest displaynameCatalog];
414 NSMutableArray *inexistentGroupUsers = [NSMutableArray array];
415 for (NSString *groupUser in allGroupUsers) {
416 if (![displaynameCatalog objectForKey:groupUser]) {
417 [inexistentGroupUsers addObject:groupUser];
420 if (!inexistentGroupUsers.count) {
422 for (NSString *groupName in translatedGroups) {
423 NSMutableArray *groupUsers = [NSMutableArray array];
424 for (NSString *groupUser in [translatedGroups objectForKey:groupName]) {
425 [groupUsers addObject:([groupUser isEqualToString:@"*"] ?
426 @"*" : [displaynameCatalog objectForKey:groupUser])];
428 [groups setObject:groupUsers forKey:groupName];
431 NSAlert *alert = [[NSAlert alloc] init];
432 if (inexistentGroupUsers.count == 1) {
433 [alert setMessageText:@"Invalid User"];
434 [alert setInformativeText:[NSString stringWithFormat:@"User '%@' doesn't exist.", [inexistentGroupUsers objectAtIndex:0]]];
436 [alert setMessageText:@"Invalid Users"];
437 [alert setInformativeText:[NSString stringWithFormat:@"Users '%@' don't exist.", [inexistentGroupUsers componentsJoinedByString:@"', '"]]];
439 [alert addButtonWithTitle:@"OK"];
444 // 404. Since we don't translate to UUIDs, check for invalid chars.
446 for (NSString *groupUser in allGroupUsers) {
447 if ([groupUser rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" ~,;:"]].location != NSNotFound) {
453 [groups addEntriesFromDictionary:translatedGroups];
455 NSAlert *alert = [[NSAlert alloc] init];
456 [alert setMessageText:@"Invalid Input"];
457 [alert setInformativeText:@"Users cannot contain ' ', '~', ',', ';' or ':'."];
458 [alert addButtonWithTitle:@"OK"];
464 [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"];
467 [groups addEntriesFromDictionary:translatedGroups];
470 [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"];
473 applyMetadataAccountRequest = [ASIPithosAccountRequest updateAccountMetadataRequestWithPithos:pithos
475 metadata:pithosAccount.metadata
477 applyMetadataAccountRequest.delegate = self;
478 applyMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
479 applyMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
480 applyMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
481 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
482 [NSNumber numberWithUnsignedInteger:10], @"retries",
483 NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector",
484 NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector",
486 [[PithosUtilities prepareRequest:applyMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
491 - (void)refreshInfo {
492 @synchronized(self) {
493 if (refreshMetadataAccountRequest == nil) {
494 refreshMetadataAccountRequest = [ASIPithosAccountRequest accountMetadataRequestWithPithos:pithos];
495 refreshMetadataAccountRequest.delegate = self;
496 refreshMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
497 refreshMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
498 refreshMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
499 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
500 [NSNumber numberWithUnsignedInteger:10], @"retries",
501 NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector",
502 NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector",
505 refreshMetadataAccountRequest.downloadCache = [ASIDownloadCache sharedCache];
506 [[PithosUtilities prepareRequest:refreshMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];