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, accountRequest, applyMetadataAccountRequest, refreshMetadataAccountRequest, translatedGroups;
55 if (self == [PithosAccountNode class])
56 sharedIcon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
60 #pragma mark Object Lifecycle
62 - (id)initWithPithosAccountManager:(PithosAccount *)aPithosAccountManager andPithos:(ASIPithos *)aPithos {
63 if ((self = [super initWithPithosAccountManager:aPithosAccountManager])) {
70 [accountRequest clearDelegatesAndCancel];
71 [refreshMetadataAccountRequest clearDelegatesAndCancel];
72 [applyMetadataAccountRequest clearDelegatesAndCancel];
78 - (void)updateGroups {
80 self.translatedGroups = [NSMutableDictionary dictionary];
81 } else if (pithosAccountManager) {
82 NSMutableSet *UUIDs = [NSMutableSet set];
83 for (NSString *groupName in pithosAccount.groups) {
84 [UUIDs addObjectsFromArray:[pithosAccount.groups objectForKey:groupName]];
86 [UUIDs removeObject:@""];
87 [UUIDs removeObject:@"*"];
89 [pithosAccountManager updateUserCatalogForDisplaynames:nil UUIDs:[UUIDs allObjects]];
92 NSMutableDictionary *newTranslatedGroups = [NSMutableDictionary dictionaryWithCapacity:pithosAccount.groups.count];
93 for (NSString *groupName in pithosAccount.groups) {
94 NSMutableArray *groupUsers = [NSMutableArray array];
95 for (NSString *UUID in [pithosAccount.groups objectForKey:groupName]) {
96 [groupUsers addObject:[pithosAccountManager displaynameForUUID:UUID safe:YES]];
98 [newTranslatedGroups setObject:groupUsers forKey:groupName];
100 self.translatedGroups = newTranslatedGroups;
102 self.translatedGroups = [pithosAccount.groups copy];
107 #pragma mark Properties
109 - (void)setPithos:(ASIPithos *)aPithos {
110 if (aPithos && ![aPithos isEqualTo:pithos]) {
113 [accountRequest clearDelegatesAndCancel];
114 self.accountRequest = nil;
115 [refreshMetadataAccountRequest clearDelegatesAndCancel];
116 self.refreshMetadataAccountRequest = nil;
117 [applyMetadataAccountRequest clearDelegatesAndCancel];
118 self.applyMetadataAccountRequest = nil;
123 - (void)setPithosAccount:(ASIPithosAccount *)aPithosAccount {
124 if (![pithosAccount isEqualTo:aPithosAccount]) {
125 pithosAccount = aPithosAccount;
132 url = [[NSString alloc] initWithFormat:@"%@%@",
133 (sharingAccount ? [pithos storageURLWithAuthUser:sharingAccount] : pithos.storageURL),
134 (shared ? @"?shared" : @"")];
138 - (NSArray *)children {
139 @synchronized(self) {
141 [accountRequest clearDelegatesAndCancel];
142 self.accountRequest = nil;
143 [refreshMetadataAccountRequest clearDelegatesAndCancel];
144 self.refreshMetadataAccountRequest = nil;
145 [applyMetadataAccountRequest clearDelegatesAndCancel];
146 self.applyMetadataAccountRequest = nil;
149 self.pithosAccount = nil;
150 freshness = PithosNodeStateRefreshNeeded;
153 [self postChildrenUpdatedNotificationName];
156 case PithosNodeStateFresh:
158 case PithosNodeStateRefreshNeeded:
159 freshness = PithosNodeStateRefreshing;
160 self.accountRequest = [ASIPithosAccountRequest listContainersRequestWithPithos:pithos
166 [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
167 else if (!forcedRefresh)
168 accountRequest.downloadCache = [ASIDownloadCache sharedCache];
169 accountRequest.delegate = self;
170 accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
171 accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
172 accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
173 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
174 [NSNumber numberWithUnsignedInteger:10], @"retries",
175 NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector",
176 NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector",
178 [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
180 case PithosNodeStateRefreshing:
182 case PithosNodeStateRefreshFinished:
184 children = newChildren;
187 freshness = PithosNodeStateFresh;
195 - (NSString *)displayName {
196 if (displayName == nil) {
197 if (!sharingAccount) {
199 } else if (pithosAccountManager) {
200 return [pithosAccountManager displaynameForUUID:sharingAccount safe:YES];
202 return [sharingAccount copy];
205 return [displayName copy];
215 #pragma mark ASIHTTPRequestDelegate
217 - (void)accountRequestFailed:(ASIPithosAccountRequest *)request {
220 NSError *error = [accountRequest error];
222 message = [NSString stringWithFormat:@"Account listing %@ failed: %@", accountRequest.url, [error localizedDescription]];
224 message = [NSString stringWithFormat:@"Account listing %@ failed: (%d) %@",
225 accountRequest.url, accountRequest.responseStatusCode, accountRequest.responseStatusMessage];
226 dispatch_async(dispatch_get_main_queue(), ^{
227 [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
229 NSUInteger retries = [[accountRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
231 ASIPithosAccountRequest *newAccountRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:accountRequest];
232 [(NSMutableDictionary *)(newAccountRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
233 self.accountRequest = newAccountRequest;
234 [[PithosUtilities prepareRequest:accountRequest priority:[[accountRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
237 self.accountRequest = nil;
239 @synchronized(self) {
240 freshness = PithosNodeStateRefreshNeeded;
246 - (void)accountRequestFinished:(ASIPithosAccountRequest *)request {
248 DLog(@"List account finished: %@", [accountRequest url]);
249 DLog(@"Cached: %d", [accountRequest didUseCachedResponse]);
250 if (accountRequest.responseStatusCode == 200) {
251 self.pithosAccount = [accountRequest account];
253 NSMutableArray *containers = [accountRequest.userInfo objectForKey:@"containers"];
254 NSArray *someContainers = [accountRequest containers];
255 if (containers == nil) {
256 containers = [NSMutableArray arrayWithArray:someContainers];
258 [containers addObjectsFromArray:someContainers];
260 if ([someContainers count] < 10000) {
261 if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) {
263 DLog(@"using newChildren");
264 newChildren = [[NSMutableArray alloc] init];
265 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
266 for (ASIPithosContainer *container in containers) {
267 PithosContainerNode *node = [[PithosContainerNode alloc] initWithPithosAccountManager:pithosAccountManager
269 pithosContainer:container];
271 node.shared = shared;
272 node.sharingAccount = sharingAccount;
273 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
275 NSUInteger oldIndex = [children indexOfObject:node];
276 if (oldIndex != NSNotFound) {
277 // Use the same pointer value, if possible
278 node = [children objectAtIndex:oldIndex];
279 // node.pithosContainer = container;
280 [node setLimitedPithosContainer:container];
281 [keptNodes addIndex:oldIndex];
284 [newChildren addObject:node];
286 [[children objectsAtIndexes:
287 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
288 if ([keptNodes containsIndex:idx])
291 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
293 // Else cache was used and all results were fetched during this request, so existing children can be reused
294 self.accountRequest = nil;
296 @synchronized(self) {
297 freshness = PithosNodeStateRefreshFinished;
299 [self postChildrenUpdatedNotificationName];
301 // Do an additional request to fetch more objects
302 self.accountRequest = [ASIPithosAccountRequest listContainersRequestWithPithos:pithos
304 marker:[[someContainers lastObject] name]
308 [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
309 else if (!forcedRefresh)
310 accountRequest.downloadCache = [ASIDownloadCache sharedCache];
311 accountRequest.delegate = self;
312 accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
313 accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
314 accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
315 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
316 [NSNumber numberWithUnsignedInteger:10], @"retries",
317 NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector",
318 NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector",
319 containers, @"containers",
321 [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
323 } else if (accountRequest.responseStatusCode == 304) {
324 // Account is not modified, so existing children can be reused
325 self.accountRequest = nil;
327 @synchronized(self) {
328 freshness = PithosNodeStateRefreshFinished;
330 [self postChildrenUpdatedNotificationName];
332 [self accountRequestFailed:accountRequest];
337 - (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request {
339 DLog(@"URL: %@", [request url]);
340 DLog(@"cached: %d", [request didUseCachedResponse]);
342 if ([request isEqualTo:applyMetadataAccountRequest]) {
343 @synchronized(self) {
344 self.applyMetadataAccountRequest = nil;
347 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
348 self.pithosAccount = [refreshMetadataAccountRequest account];
349 @synchronized(self) {
350 self.refreshMetadataAccountRequest = nil;
356 - (void)accountMetadataRequestFailed:(ASIPithosAccountRequest *)request {
358 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
360 ASIPithosAccountRequest *newRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:request];
361 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
362 if ([request isEqualTo:applyMetadataAccountRequest]) {
363 @synchronized(self) {
364 self.applyMetadataAccountRequest = newRequest;
366 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
367 @synchronized(self) {
368 self.refreshMetadataAccountRequest = newRequest;
371 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
373 if ([request isEqualTo:applyMetadataAccountRequest]) {
374 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataAccountRequest];
375 @synchronized(self) {
376 self.applyMetadataAccountRequest = nil;
378 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
379 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataAccountRequest];
380 @synchronized(self) {
381 self.refreshMetadataAccountRequest = nil;
392 @synchronized(self) {
393 if (applyMetadataAccountRequest == nil) {
394 NSMutableDictionary *groups = [NSMutableDictionary dictionary];
395 if (translatedGroups.count) {
396 for (NSString *groupName in translatedGroups) {
397 if (!groupName.length ||
398 [groupName rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound) {
399 NSAlert *alert = [[NSAlert alloc] init];
400 [alert setMessageText:@"Invalid Input"];
401 [alert setInformativeText:@"Group names cannot be empty or contain ' ', '-', '_', '~', ',' or ';'."];
402 [alert addButtonWithTitle:@"OK"];
407 if (pithosAccountManager) {
408 NSMutableSet *allGroupUsers = [NSMutableSet set];
409 for (NSMutableArray *groupUsers in [translatedGroups objectEnumerator]) {
410 [allGroupUsers addObjectsFromArray:groupUsers];
412 [allGroupUsers removeObject:@""];
413 [allGroupUsers removeObject:@"*"];
414 if (allGroupUsers.count) {
415 ASIPithosRequest *userCatalogRequest = [pithosAccountManager updateUserCatalogForDisplaynames:[allGroupUsers allObjects]
417 if (userCatalogRequest.error || ((userCatalogRequest.responseStatusCode != 200) && (userCatalogRequest.responseStatusCode != 404))) {
419 } else if (userCatalogRequest.responseStatusCode == 200) {
420 // Check if all users exist.
421 NSDictionary *displaynameCatalog = [userCatalogRequest displaynameCatalog];
422 NSMutableArray *inexistentGroupUsers = [NSMutableArray array];
423 for (NSString *groupUser in allGroupUsers) {
424 if (![displaynameCatalog objectForKey:groupUser]) {
425 [inexistentGroupUsers addObject:groupUser];
428 if (!inexistentGroupUsers.count) {
430 for (NSString *groupName in translatedGroups) {
431 NSMutableArray *groupUsers = [NSMutableArray array];
432 for (NSString *groupUser in [translatedGroups objectForKey:groupName]) {
433 [groupUsers addObject:([groupUser isEqualToString:@"*"] ?
434 @"*" : [displaynameCatalog objectForKey:groupUser])];
436 [groups setObject:groupUsers forKey:groupName];
439 NSAlert *alert = [[NSAlert alloc] init];
440 if (inexistentGroupUsers.count == 1) {
441 [alert setMessageText:@"Invalid User"];
442 [alert setInformativeText:[NSString stringWithFormat:@"User '%@' doesn't exist.", [inexistentGroupUsers objectAtIndex:0]]];
444 [alert setMessageText:@"Invalid Users"];
445 [alert setInformativeText:[NSString stringWithFormat:@"Users '%@' don't exist.", [inexistentGroupUsers componentsJoinedByString:@"', '"]]];
447 [alert addButtonWithTitle:@"OK"];
452 // 404. Since we don't translate to UUIDs, check for invalid chars.
454 for (NSString *groupUser in allGroupUsers) {
455 if ([groupUser rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" ~,;:"]].location != NSNotFound) {
461 [groups addEntriesFromDictionary:translatedGroups];
463 NSAlert *alert = [[NSAlert alloc] init];
464 [alert setMessageText:@"Invalid Input"];
465 [alert setInformativeText:@"Users cannot contain ' ', '~', ',', ';' or ':'."];
466 [alert addButtonWithTitle:@"OK"];
472 for (NSString *groupName in translatedGroups) {
473 if ([[translatedGroups objectForKey:groupName] containsObject:@"*"]) {
474 [groups setObject:[NSMutableArray arrayWithObject:@"*"] forKey:groupName];
478 [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"];
482 [groups addEntriesFromDictionary:translatedGroups];
485 [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"];
488 self.applyMetadataAccountRequest = [ASIPithosAccountRequest updateAccountMetadataRequestWithPithos:pithos
490 metadata:pithosAccount.metadata
492 applyMetadataAccountRequest.delegate = self;
493 applyMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
494 applyMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
495 applyMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
496 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
497 [NSNumber numberWithUnsignedInteger:10], @"retries",
498 NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector",
499 NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector",
501 [[PithosUtilities prepareRequest:applyMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
506 - (void)refreshInfo {
507 @synchronized(self) {
508 if (refreshMetadataAccountRequest == nil) {
509 self.refreshMetadataAccountRequest = [ASIPithosAccountRequest accountMetadataRequestWithPithos:pithos];
510 refreshMetadataAccountRequest.delegate = self;
511 refreshMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
512 refreshMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
513 refreshMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
514 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
515 [NSNumber numberWithUnsignedInteger:10], @"retries",
516 NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector",
517 NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector",
520 refreshMetadataAccountRequest.downloadCache = [ASIDownloadCache sharedCache];
521 [[PithosUtilities prepareRequest:refreshMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];