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 return [NSString stringWithString:(sharingAccount ? sharingAccount : @"account")];
190 return [displayName copy];
200 #pragma mark ASIHTTPRequestDelegate
202 - (void)accountRequestFailed:(ASIPithosAccountRequest *)request {
205 NSError *error = [accountRequest error];
207 message = [NSString stringWithFormat:@"Account listing %@ failed: %@", accountRequest.url, [error localizedDescription]];
209 message = [NSString stringWithFormat:@"Account listing %@ failed: (%d) %@",
210 accountRequest.url, accountRequest.responseStatusCode, accountRequest.responseStatusMessage];
211 dispatch_async(dispatch_get_main_queue(), ^{
212 [[PithosActivityFacility defaultPithosActivityFacility] startAndEndActivityWithType:PithosActivityOther message:message];
214 NSUInteger retries = [[accountRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
216 ASIPithosAccountRequest *newAccountRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:accountRequest];
217 [(NSMutableDictionary *)(newAccountRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
218 accountRequest = newAccountRequest;
219 [[PithosUtilities prepareRequest:accountRequest priority:[[accountRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
222 accountRequest = nil;
225 @synchronized(self) {
226 freshness = PithosNodeStateRefreshNeeded;
232 - (void)accountRequestFinished:(ASIPithosAccountRequest *)request {
234 DLog(@"List account finished: %@", [accountRequest url]);
235 DLog(@"Cached: %d", [accountRequest didUseCachedResponse]);
236 if (accountRequest.responseStatusCode == 200) {
237 self.pithosAccount = [accountRequest account];
239 NSArray *someContainers = [accountRequest containers];
240 if (containers == nil) {
241 containers = [[NSMutableArray alloc] initWithArray:someContainers];
243 [containers addObjectsFromArray:someContainers];
245 if ([someContainers count] < 10000) {
246 if (!accountRequest.didUseCachedResponse || ([containers count] != [someContainers count]) || !children) {
248 DLog(@"using newChildren");
249 newChildren = [[NSMutableArray alloc] init];
250 NSMutableIndexSet *keptNodes = [NSMutableIndexSet indexSet];
251 for (ASIPithosContainer *container in containers) {
252 PithosContainerNode *node = [[PithosContainerNode alloc] initWithPithos:pithos pithosContainer:container];
254 node.shared = shared;
255 node.sharingAccount = sharingAccount;
256 node.inheritChildrenUpdatedNotificationName = inheritChildrenUpdatedNotificationName;
258 NSUInteger oldIndex = [children indexOfObject:node];
259 if (oldIndex != NSNotFound) {
260 // Use the same pointer value, if possible
261 node = [children objectAtIndex:oldIndex];
262 // node.pithosContainer = container;
263 [node setLimitedPithosContainer:container];
264 [keptNodes addIndex:oldIndex];
267 [newChildren addObject:node];
269 [[children objectsAtIndexes:
270 [[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [children count])] indexesPassingTest:^(NSUInteger idx, BOOL *stop){
271 if ([keptNodes containsIndex:idx])
274 }]] makeObjectsPerformSelector:@selector(pithosNodeWillBeRemoved)];
276 // Else cache was used and all results were fetched during this request, so existing children can be reused
277 accountRequest = nil;
280 @synchronized(self) {
281 freshness = PithosNodeStateRefreshFinished;
283 [self postChildrenUpdatedNotificationName];
285 // Do an additional request to fetch more objects
286 accountRequest = [ASIPithosAccountRequest listContainersRequestWithPithos:pithos
288 marker:[[someContainers lastObject] name]
292 [accountRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos];
293 else if (!forcedRefresh)
294 accountRequest.downloadCache = [ASIDownloadCache sharedCache];
295 accountRequest.delegate = self;
296 accountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
297 accountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
298 accountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
299 [NSNumber numberWithInteger:NSOperationQueuePriorityVeryHigh], @"priority",
300 [NSNumber numberWithUnsignedInteger:10], @"retries",
301 NSStringFromSelector(@selector(accountRequestFinished:)), @"didFinishSelector",
302 NSStringFromSelector(@selector(accountRequestFailed:)), @"didFailSelector",
304 [[PithosUtilities prepareRequest:accountRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
306 } else if (accountRequest.responseStatusCode == 304) {
307 // Account is not modified, so existing children can be reused
308 accountRequest = nil;
311 @synchronized(self) {
312 freshness = PithosNodeStateRefreshFinished;
314 [self postChildrenUpdatedNotificationName];
316 [self accountRequestFailed:accountRequest];
321 - (void)accountMetadataRequestFinished:(ASIPithosAccountRequest *)request {
323 DLog(@"URL: %@", [request url]);
324 DLog(@"cached: %d", [request didUseCachedResponse]);
326 if ([request isEqualTo:applyMetadataAccountRequest]) {
327 @synchronized(self) {
328 applyMetadataAccountRequest = nil;
331 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
332 self.pithosAccount = [refreshMetadataAccountRequest account];
333 @synchronized(self) {
334 refreshMetadataAccountRequest = nil;
340 - (void)accountMetadataRequestFailed:(ASIPithosAccountRequest *)request {
342 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
344 ASIPithosAccountRequest *newRequest = (ASIPithosAccountRequest *)[PithosUtilities copyRequest:request];
345 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
346 if ([request isEqualTo:applyMetadataAccountRequest]) {
347 @synchronized(self) {
348 applyMetadataAccountRequest = newRequest;
350 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
351 @synchronized(self) {
352 refreshMetadataAccountRequest = newRequest;
355 [[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]] startAsynchronous];
357 if ([request isEqualTo:applyMetadataAccountRequest]) {
358 [PithosUtilities httpRequestErrorAlertWithRequest:applyMetadataAccountRequest];
359 @synchronized(self) {
360 applyMetadataAccountRequest = nil;
362 } else if ([request isEqualTo:refreshMetadataAccountRequest]) {
363 [PithosUtilities httpRequestErrorAlertWithRequest:refreshMetadataAccountRequest];
364 @synchronized(self) {
365 refreshMetadataAccountRequest = nil;
376 @synchronized(self) {
377 if (applyMetadataAccountRequest == nil) {
378 NSMutableDictionary *groups = [NSMutableDictionary dictionary];
379 if (translatedGroups.count) {
380 for (NSString *groupName in translatedGroups) {
381 if (!groupName.length ||
382 [groupName rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" -_~,;"]].location != NSNotFound) {
383 NSAlert *alert = [[NSAlert alloc] init];
384 [alert setMessageText:@"Invalid Input"];
385 [alert setInformativeText:@"Group names cannot be empty or contain ' ', '-', '_', '~', ',' or ';'."];
386 [alert addButtonWithTitle:@"OK"];
391 if (pithosAccountManager) {
392 NSMutableSet *allGroupUsers = [NSMutableSet set];
393 for (NSMutableArray *groupUsers in [translatedGroups objectEnumerator]) {
394 [allGroupUsers addObjectsFromArray:groupUsers];
396 [allGroupUsers removeObject:@""];
397 [allGroupUsers removeObject:@"*"];
398 if (allGroupUsers.count) {
399 ASIPithosRequest *userCatalogRequest = [pithosAccountManager updateUserCatalogForForDisplaynames:[allGroupUsers allObjects]
401 if (userCatalogRequest.error || ((userCatalogRequest.responseStatusCode != 200) && (userCatalogRequest.responseStatusCode != 404))) {
403 } else if (userCatalogRequest.responseStatusCode == 200) {
404 // Check if all users exist.
405 NSDictionary *displaynameCatalog = [userCatalogRequest displaynameCatalog];
406 NSMutableArray *inexistentGroupUsers = [NSMutableArray array];
407 for (NSString *groupUser in allGroupUsers) {
408 if (![displaynameCatalog objectForKey:groupUser]) {
409 [inexistentGroupUsers addObject:groupUser];
412 if (!inexistentGroupUsers.count) {
414 for (NSString *groupName in translatedGroups) {
415 NSMutableArray *groupUsers = [NSMutableArray array];
416 for (NSString *groupUser in [translatedGroups objectForKey:groupName]) {
417 [groupUsers addObject:([groupUser isEqualToString:@"*"] ?
418 @"*" : [displaynameCatalog objectForKey:groupUser])];
420 [groups setObject:groupUsers forKey:groupName];
423 NSAlert *alert = [[NSAlert alloc] init];
424 if (inexistentGroupUsers.count == 1) {
425 [alert setMessageText:@"Invalid User"];
426 [alert setInformativeText:[NSString stringWithFormat:@"User '%@' doesn't exist.", [inexistentGroupUsers objectAtIndex:0]]];
428 [alert setMessageText:@"Invalid Users"];
429 [alert setInformativeText:[NSString stringWithFormat:@"Users '%@' don't exist.", [inexistentGroupUsers componentsJoinedByString:@"', '"]]];
431 [alert addButtonWithTitle:@"OK"];
436 // 404. Since we don't translate to UUIDs, check for invalid chars.
438 for (NSString *groupUser in allGroupUsers) {
439 if ([groupUser rangeOfCharacterFromSet:[NSCharacterSet characterSetWithCharactersInString:@" ~,;:"]].location != NSNotFound) {
445 [groups addEntriesFromDictionary:translatedGroups];
447 NSAlert *alert = [[NSAlert alloc] init];
448 [alert setMessageText:@"Invalid Input"];
449 [alert setInformativeText:@"Users cannot contain ' ', '~', ',', ';' or ':'."];
450 [alert addButtonWithTitle:@"OK"];
456 [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"];
459 [groups addEntriesFromDictionary:translatedGroups];
462 [groups setObject:[NSArray arrayWithObject:@""] forKey:@"group"];
465 applyMetadataAccountRequest = [ASIPithosAccountRequest updateAccountMetadataRequestWithPithos:pithos
467 metadata:pithosAccount.metadata
469 applyMetadataAccountRequest.delegate = self;
470 applyMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
471 applyMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
472 applyMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
473 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
474 [NSNumber numberWithUnsignedInteger:10], @"retries",
475 NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector",
476 NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector",
478 [[PithosUtilities prepareRequest:applyMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];
483 - (void)refreshInfo {
484 @synchronized(self) {
485 if (refreshMetadataAccountRequest == nil) {
486 refreshMetadataAccountRequest = [ASIPithosAccountRequest accountMetadataRequestWithPithos:pithos];
487 refreshMetadataAccountRequest.delegate = self;
488 refreshMetadataAccountRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
489 refreshMetadataAccountRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
490 refreshMetadataAccountRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
491 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
492 [NSNumber numberWithUnsignedInteger:10], @"retries",
493 NSStringFromSelector(@selector(accountMetadataRequestFinished:)), @"didFinishSelector",
494 NSStringFromSelector(@selector(accountMetadataRequestFailed:)), @"didFailSelector",
497 refreshMetadataAccountRequest.downloadCache = [ASIDownloadCache sharedCache];
498 [[PithosUtilities prepareRequest:refreshMetadataAccountRequest priority:NSOperationQueuePriorityHigh] startAsynchronous];