2 // PithosBrowserController.m
5 // Copyright 2011 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 "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosSharingAccountsNode.h"
45 #import "PithosEmptyNode.h"
46 #import "ImageAndTextCell.h"
47 #import "FileSystemBrowserCell.h"
48 #import "ASIPithosRequest.h"
49 #import "ASIPithosContainerRequest.h"
50 #import "ASIPithosObjectRequest.h"
51 #import "ASIPithosContainer.h"
52 #import "ASIPithosObject.h"
53 #import "PithosFileUtilities.h"
55 @interface PithosBrowserCell : FileSystemBrowserCell {}
58 @implementation PithosBrowserCell
61 if ((self = [super init])) {
62 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
63 [self setEditable:YES];
68 - (void)setObjectValue:(id)object {
69 if ([object isKindOfClass:[PithosNode class]]) {
70 PithosNode *node = (PithosNode *)object;
71 [self setStringValue:node.displayName];
72 [self setImage:node.icon];
74 [super setObjectValue:object];
80 @interface PithosOutlineViewCell : ImageAndTextCell {}
83 @implementation PithosOutlineViewCell
85 - (void)setObjectValue:(id)object {
86 if ([object isKindOfClass:[PithosNode class]]) {
87 PithosNode *node = (PithosNode *)object;
88 [self setStringValue:node.displayName];
89 [self setImage:node.icon];
90 [self setEditable:NO];
92 [super setObjectValue:object];
98 @interface PithosBrowserController (Private) {}
99 - (void)resetContainers:(NSNotification *)notification;
102 @implementation PithosBrowserController
103 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser;
104 @synthesize draggedNodes, draggedParentNode;
105 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
108 #pragma Object Lifecycle
111 return [super initWithWindowNibName:@"PithosBrowserController"];
115 [[NSNotificationCenter defaultCenter] removeObserver:self];
116 [clipboardParentNode release];
117 [clipboardNodes release];
118 [draggedParentNode release];
119 [draggedNodes release];
120 [browserMenu release];
121 [sharedPreviewController release];
122 [othersSharedNode release];
123 [mySharedNode release];
124 [sharedNode release];
125 [containersNodeChildren release];
126 [containersNode release];
127 [accountNode release];
132 - (void)awakeFromNib {
133 [super awakeFromNib];
135 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
136 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
137 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
139 [browser setCellClass:[PithosBrowserCell class]];
141 browserMenu = [[NSMenu alloc] init];
142 [browserMenu setDelegate:self];
143 [browser setMenu:browserMenu];
146 - (void)resetContainers:(NSNotification *)notification {
148 [browser loadColumnZero];
149 [containersNodeChildren removeAllObjects];
150 [outlineView reloadData];
151 // Expand the folder outline view
152 [outlineView expandItem:nil expandChildren:YES];
153 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
156 [accountNode refresh];
157 [mySharedNode refresh];
158 [othersSharedNode refresh];
161 - (void)windowDidLoad {
162 [super windowDidLoad];
164 accountNode = [[PithosAccountNode alloc] init];
165 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
166 containersNodeChildren = [[NSMutableArray alloc] init];
167 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
168 mySharedNode = [[PithosAccountNode alloc] init];
169 mySharedNode.displayName = @"my shared";
170 mySharedNode.shared = YES;
171 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
172 othersSharedNode = [[PithosSharingAccountsNode alloc] init];
173 othersSharedNode.displayName = @"others shared";
174 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
176 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
178 // Register for updates
179 // PithosContainerNode updates browser nodes
180 [[NSNotificationCenter defaultCenter] addObserver:self
181 selector:@selector(pithosNodeChildrenUpdated:)
182 name:@"PithosContainerNodeChildrenUpdated"
184 // PithosSubdirNode updates browser nodes
185 [[NSNotificationCenter defaultCenter] addObserver:self
186 selector:@selector(pithosNodeChildrenUpdated:)
187 name:@"PithosSubdirNodeChildrenUpdated"
189 // PithosAccountNode accountNode updates outlineView container nodes
190 [[NSNotificationCenter defaultCenter] addObserver:self
191 selector:@selector(pithosAccountNodeChildrenUpdated:)
192 name:@"PithosAccountNodeChildrenUpdated"
194 // PithosAccountNode other than accountNode updates nodes
195 [[NSNotificationCenter defaultCenter] addObserver:self
196 selector:@selector(pithosNodeChildrenUpdated:)
197 name:@"PithosAccountNodeChildrenUpdated"
199 // PithosSharingAccountsNode othersSharedNode updates browser nodes
200 [[NSNotificationCenter defaultCenter] addObserver:self
201 selector:@selector(pithosNodeChildrenUpdated:)
202 name:@"PithosSharingAccountsNodeChildrenUpdated"
203 object:othersSharedNode];
204 // Updated authentication credentials reset containers in the outline view
205 [[NSNotificationCenter defaultCenter] addObserver:self
206 selector:@selector(resetContainers:)
207 name:@"PithosAuthenticationCredentialsUpdated"
209 // Request for browser refresh
210 [[NSNotificationCenter defaultCenter] addObserver:self
211 selector:@selector(pithosBrowserRefreshNeeded:)
212 name:@"PithosBrowserRefreshNeeeded"
217 #pragma mark Observers
219 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
220 PithosNode *node = (PithosNode *)[notification object];
221 if (node == accountNode)
223 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
224 NSInteger lastColumn = [browser lastColumn];
225 for (NSInteger column = lastColumn; column >= 0; column--) {
226 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
227 [browser reloadColumn:column];
233 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
234 BOOL containerPithosFound = NO;
235 BOOL containerTrashFound = NO;
236 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
237 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
238 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
239 [removedContainersNodeChildren addIndex:i];
241 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
242 for (PithosContainerNode *containerNode in accountNode.children) {
243 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
244 if (![containersNodeChildren containsObject:containerNode])
245 [containersNodeChildren insertObject:containerNode atIndex:0];
246 containerPithosFound = YES;
247 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
248 NSUInteger insertIndex = 1;
249 if (!containerPithosFound)
251 if (![containersNodeChildren containsObject:containerNode])
252 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
253 containerTrashFound = YES;
254 } else if (![containersNodeChildren containsObject:containerNode]) {
255 [containersNodeChildren addObject:containerNode];
258 BOOL refreshAccountNode = NO;
259 if (!containerPithosFound) {
260 // Create pithos node
261 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
262 [containerRequest startSynchronous];
263 if ([containerRequest error]) {
264 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
266 refreshAccountNode = YES;
269 if (!containerTrashFound) {
271 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
272 [containerRequest startSynchronous];
273 if ([containerRequest error]) {
274 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
276 refreshAccountNode = YES;
280 if (refreshAccountNode)
281 [accountNode refresh];
283 [outlineView reloadData];
285 // Expand the folder outline view
286 [outlineView expandItem:nil expandChildren:YES];
288 if ((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) {
289 rootNode = [containersNodeChildren objectAtIndex:0];
290 [browser loadColumnZero];
297 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
304 - (IBAction)refresh:(id)sender {
305 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
307 [accountNode forceRefresh];
308 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
309 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
310 node.forcedRefresh = YES;
311 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
312 //[(PithosNode *)[browser parentForItemsInColumn:column] forceRefresh];
316 [accountNode refresh];
317 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
318 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
321 [browser validateVisibleColumns];
325 #pragma mark NSBrowserDelegate
327 - (id)rootItemForBrowser:(NSBrowser *)browser {
331 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
332 PithosNode *node = (PithosNode *)item;
333 return node.children.count;
336 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
337 PithosNode *node = (PithosNode *)item;
338 return [node.children objectAtIndex:index];
341 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
342 PithosNode *node = (PithosNode *)item;
343 return node.isLeafItem;
346 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
347 PithosNode *node = (PithosNode *)item;
351 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
352 if (sharedPreviewController == nil)
353 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
354 return sharedPreviewController;
357 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
358 // if (!forUserResize) {
359 // id item = [browser parentForItemsInColumn:columnIndex];
360 // if ([self browser:browser isLeafItem:item]) {
361 // suggestedWidth = 200;
364 // return suggestedWidth;
367 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
373 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
374 PithosNode *node = (PithosNode *)item;
375 if (node.shared || node.sharingAccount ||
376 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
381 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
382 PithosNode *node = (PithosNode *)item;
383 NSString *newName = (NSString *)object;
384 NSUInteger newNameLength = [newName length];
385 NSRange firstSlashRange = [newName rangeOfString:@"/"];
386 if ((newNameLength == 0) ||
387 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
388 ([newName isEqualToString:node.displayName])) {
391 if (([node class] == [PithosObjectNode class]) ||
392 (([node class] == [PithosSubdirNode class]) &&
393 !node.pithosObject.subdir &&
394 [node.pithosObject.name hasSuffix:@"/"])) {
395 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
396 dispatch_async(queue, ^{
397 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
398 if ([newName hasSuffix:@"/"])
399 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
400 NSError *error = nil;
402 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
403 objectName:destinationObjectName
405 isDirectory:&isDirectory
406 sharingAccount:nil]) {
407 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
408 [alert setMessageText:@"Name Taken"];
409 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
410 [alert addButtonWithTitle:@"OK"];
416 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
417 objectName:node.pithosObject.name
418 destinationContainerName:node.pithosContainer.name
419 destinationObjectName:destinationObjectName
422 objectRequest.delegate = self;
423 objectRequest.didFinishSelector = @selector(moveFinished:);
424 objectRequest.didFailSelector = @selector(moveFailed:);
425 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
426 node.parent, @"node",
428 [objectRequest startAsynchronous];
431 } else if ([node class] == [PithosSubdirNode class]) {
432 if (firstSlashRange.length == 1)
434 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
435 dispatch_async(queue, ^{
436 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
437 NSError *error = nil;
439 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
440 objectName:destinationObjectName
442 isDirectory:&isDirectory
443 sharingAccount:nil]) {
444 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
445 [alert setMessageText:@"Name Taken"];
446 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
447 [alert addButtonWithTitle:@"OK"];
453 if (node.pithosObject.subdir)
454 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
455 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
456 objectName:node.pithosObject.name
457 destinationContainerName:node.pithosContainer.name
458 destinationObjectName:destinationObjectName
460 if (objectRequests) {
461 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
462 objectRequest.delegate = self;
463 objectRequest.didFinishSelector = @selector(moveFinished:);
464 objectRequest.didFailSelector = @selector(moveFailed:);
465 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
466 node.parent, @"node",
468 [objectRequest startAsynchronous];
475 #pragma mark Drag and Drop source
477 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
478 withEvent:(NSEvent *)event {
479 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
480 __block BOOL result = YES;
481 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
482 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
483 if ([node class] == [PithosContainerNode class]) {
491 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
492 toPasteboard:(NSPasteboard *)pasteboard {
493 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
494 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
495 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
496 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
497 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
498 [propertyList addObject:[node.pithosObject.name pathExtension]];
499 [nodes addObject:node];
502 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
503 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
504 self.draggedNodes = nodes;
505 self.draggedParentNode = [browser parentForItemsInColumn:column];
509 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
510 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
511 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
512 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
513 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
514 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
516 // If the node is a subdir ask if the whole tree should be downloaded
517 if ([node class] == [PithosSubdirNode class]) {
518 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
519 [alert setMessageText:@"Download directory"];
520 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
521 [alert addButtonWithTitle:@"OK"];
522 [alert addButtonWithTitle:@"Cancel"];
523 NSInteger choice = [alert runModal];
524 if (choice == NSAlertFirstButtonReturn) {
525 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
526 dispatch_async(queue, ^{
527 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
528 objectName:node.pithosObject.name
529 toDirectory:[dropDestination path]
531 sharingAccount:node.sharingAccount];
532 if (objectRequests) {
533 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
534 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
535 objectRequest.delegate = self;
536 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
537 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
538 [objectRequest startAsynchronous];
544 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
545 objectName:node.pithosObject.name
546 toDirectory:[dropDestination path]
548 sharingAccount:node.sharingAccount];
550 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
551 objectRequest.delegate = self;
552 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
553 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
554 [objectRequest startAsynchronous];
561 #pragma mark Drag and Drop destination
563 - (NSDragOperation)browser:aBrowser
564 validateDrop:(id<NSDraggingInfo>)info
565 proposedRow:(NSInteger *)row
566 column:(NSInteger *)column
567 dropOperation:(NSBrowserDropOperation *)dropOperation {
568 NSDragOperation result = NSDragOperationNone;
569 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
570 // For a drop above, the drop is redirected to the parent item
571 if (*dropOperation == NSBrowserDropAbove)
573 // Only allow dropping in folders
575 PithosNode *dropNode;
577 // Check if the node is not a folder and if so redirect to the parent item
578 dropNode = [browser itemAtRow:*row inColumn:*column];
579 if ([dropNode class] == [PithosObjectNode class])
583 dropNode = [browser parentForItemsInColumn:*column];
585 if (!dropNode.shared &&
586 (!dropNode.sharingAccount ||
587 ([dropNode class] == [PithosSubdirNode class]) ||
588 ([dropNode class] == [PithosContainerNode class]))) {
589 *dropOperation = NSBrowserDropOn;
590 result = NSDragOperationCopy;
593 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
594 // For a drop above, the drop is redirected to the parent item
595 if (*dropOperation == NSBrowserDropAbove)
597 // Only allow dropping in folders
599 PithosNode *dropNode;
601 // Check if the node is not a folder and if so redirect to the parent item
602 dropNode = [browser itemAtRow:*row inColumn:*column];
603 if ([dropNode class] == [PithosObjectNode class])
607 dropNode = [browser parentForItemsInColumn:*column];
609 if (!dropNode.shared && !dropNode.sharingAccount) {
610 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
611 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
612 if ((([dropNode class] == [PithosContainerNode class]) ||
613 dropNode.pithosObject.subdir ||
614 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
615 ![dropNode isEqualTo:draggedParentNode]) {
616 // ![dropNode isEqualTo:draggedParentNode] &&
617 // ![draggedNodes containsObject:dropNode]) {
618 result = NSDragOperationMove;
620 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
621 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
622 if (([dropNode class] == [PithosContainerNode class]) ||
623 dropNode.pithosObject.subdir ||
624 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
625 result = NSDragOperationCopy;
634 - (BOOL)browser:(NSBrowser *)aBrowser
635 acceptDrop:(id<NSDraggingInfo>)info
637 column:(NSInteger)column
638 dropOperation:(NSBrowserDropOperation)dropOperation {
639 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
640 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
641 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
642 if ((column != -1) && (filenames != nil)) {
643 PithosNode *node = nil;
645 node = [browser itemAtRow:row inColumn:column];
647 node = [browser parentForItemsInColumn:column];
648 NSLog(@"drag in node: %@", node.url);
649 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
652 NSFileManager *defaultManager = [NSFileManager defaultManager];
653 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
654 NSString *objectNamePrefix;
655 if ([node class] == [PithosSubdirNode class])
656 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
658 objectNamePrefix = [NSString string];
659 NSUInteger blockSize = node.pithosContainer.blockSize;
660 NSString *blockHash = node.pithosContainer.blockHash;
662 for (NSString *filePath in filenames) {
664 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
667 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
668 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
669 dispatch_async(queue, ^{
670 NSError *error = nil;
671 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
672 if (contentType == nil)
673 contentType = @"application/octet-stream";
675 NSLog(@"contentType detection error: %@", error);
676 NSArray *hashes = nil;
677 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
678 objectName:objectName
679 contentType:contentType
685 sharingAccount:node.sharingAccount];
687 objectRequest.delegate = self;
688 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
689 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
690 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
691 containerName, @"containerName",
692 objectName, @"objectName",
693 contentType, @"contentType",
694 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
695 blockHash, @"blockHash",
696 filePath, @"filePath",
699 [NSNumber numberWithUnsignedInteger:10], @"iteration",
701 if (node.sharingAccount)
702 [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
703 objectRequest.userInfo = userInfo;
704 [objectRequest startAsynchronous];
708 // Upload directory, confirm first
709 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
710 [alert setMessageText:@"Upload directory"];
711 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
712 [alert addButtonWithTitle:@"OK"];
713 [alert addButtonWithTitle:@"Cancel"];
714 NSInteger choice = [alert runModal];
715 if (choice == NSAlertFirstButtonReturn) {
716 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
717 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
718 dispatch_async(queue, ^{
719 NSMutableArray *objectNames = nil;
720 NSMutableArray *contentTypes = nil;
721 NSMutableArray *filePaths = nil;
722 NSMutableArray *hashesArrays = nil;
723 NSMutableArray *directoryObjectRequests = nil;
724 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
725 objectName:objectName
728 forDirectory:filePath
730 objectNames:&objectNames
731 contentTypes:&contentTypes
733 hashesArrays:&hashesArrays
734 directoryObjectRequests:&directoryObjectRequests
735 sharingAccount:node.sharingAccount];
736 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
737 objectRequest.delegate = self;
738 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
739 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
740 [objectRequest startAsynchronous];
742 if (objectRequests) {
743 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
744 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
745 objectRequest.delegate = self;
746 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
747 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
748 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
749 containerName, @"containerName",
750 [objectNames objectAtIndex:i], @"objectName",
751 [contentTypes objectAtIndex:i], @"contentType",
752 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
753 blockHash, @"blockHash",
754 [filePaths objectAtIndex:i], @"filePath",
755 [hashesArrays objectAtIndex:i], @"hashes",
756 [NSNumber numberWithUnsignedInteger:10], @"iteration",
758 if (node.sharingAccount)
759 [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
760 objectRequest.userInfo = userInfo;
761 [objectRequest startAsynchronous];
772 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
773 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
774 if ((column != -1) && (draggedNodes != nil)) {
775 PithosNode *dropNode = nil;
777 dropNode = [browser itemAtRow:row inColumn:column];
779 dropNode = [browser parentForItemsInColumn:column];
780 NSLog(@"drag local node: %@", dropNode.url);
781 if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
784 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
785 NSString *objectNamePrefix;
786 if ([dropNode class] == [PithosSubdirNode class])
787 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
789 objectNamePrefix = [NSString string];
791 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
792 for (PithosNode *node in draggedNodes) {
793 if (([node class] == [PithosObjectNode class]) ||
794 (([node class] == [PithosSubdirNode class]) &&
795 !node.pithosObject.subdir &&
796 [node.pithosObject.name hasSuffix:@"/"])) {
797 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
798 dispatch_async(queue, ^{
799 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
800 if ([node.pithosObject.name hasSuffix:@"/"])
801 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
802 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
803 objectName:node.pithosObject.name
804 destinationContainerName:containerName
805 destinationObjectName:destinationObjectName
808 objectRequest.delegate = self;
809 objectRequest.didFinishSelector = @selector(moveFinished:);
810 objectRequest.didFailSelector = @selector(moveFailed:);
811 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
812 node.parent, @"node",
813 dropNode, @"dropNode",
815 [objectRequest startAsynchronous];
818 } else if ([node class] == [PithosSubdirNode class]) {
819 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
820 dispatch_async(queue, ^{
821 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
822 if (node.pithosObject.subdir)
823 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
824 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
825 objectName:node.pithosObject.name
826 destinationContainerName:containerName
827 destinationObjectName:destinationObjectName
829 if (objectRequests) {
830 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
831 objectRequest.delegate = self;
832 objectRequest.didFinishSelector = @selector(moveFinished:);
833 objectRequest.didFailSelector = @selector(moveFailed:);
834 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
835 node.parent, @"node",
836 dropNode, @"dropNode",
838 [objectRequest startAsynchronous];
844 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
845 for (PithosNode *node in draggedNodes) {
846 if (([node class] == [PithosObjectNode class]) ||
847 (([node class] == [PithosSubdirNode class]) &&
848 !node.pithosObject.subdir &&
849 [node.pithosObject.name hasSuffix:@"/"])) {
850 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
851 dispatch_async(queue, ^{
852 NSString *destinationObjectName;
853 if (![dropNode isEqualTo:node.parent]) {
854 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
855 if ([node.pithosObject.name hasSuffix:@"/"])
856 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
858 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
859 objectName:node.pithosObject.name];
861 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
862 objectName:node.pithosObject.name
863 destinationContainerName:containerName
864 destinationObjectName:destinationObjectName
868 objectRequest.delegate = self;
869 objectRequest.didFinishSelector = @selector(copyFinished:);
870 objectRequest.didFailSelector = @selector(copyFailed:);
871 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
872 dropNode, @"dropNode",
874 [objectRequest startAsynchronous];
877 } else if ([node class] == [PithosSubdirNode class]) {
878 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
879 dispatch_async(queue, ^{
880 NSString *destinationObjectName;
881 if (![dropNode isEqualTo:node.parent]) {
882 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
883 if (node.pithosObject.subdir)
884 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
886 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
887 subdirName:node.pithosObject.name];
889 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
890 objectName:node.pithosObject.name
891 destinationContainerName:containerName
892 destinationObjectName:destinationObjectName
895 if (objectRequests) {
896 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
897 objectRequest.delegate = self;
898 objectRequest.didFinishSelector = @selector(copyFinished:);
899 objectRequest.didFailSelector = @selector(copyFailed:);
900 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
901 dropNode, @"dropNode",
903 [objectRequest startAsynchronous];
918 #pragma mark ASIHTTPRequestDelegate
920 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
921 NSLog(@"Download completed: %@", [objectRequest url]);
922 if (objectRequest.responseStatusCode == 200) {
923 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
924 NSLog(@"Downloaded 0 bytes");
925 NSFileManager *defaultManager = [NSFileManager defaultManager];
926 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
927 if (![defaultManager fileExistsAtPath:filePath]) {
928 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
929 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
930 [alert setMessageText:@"Create File Error"];
931 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
932 [alert addButtonWithTitle:@"OK"];
938 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
942 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
943 NSLog(@"Download failed");
944 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
947 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
948 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
949 if (objectRequest.responseStatusCode == 201) {
950 NSLog(@"Directory object created: %@", [objectRequest url]);
953 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
957 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
958 NSLog(@"Upload directory object failed");
959 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
962 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
963 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
964 if (objectRequest.responseStatusCode == 201) {
965 NSLog(@"Object created: %@", [objectRequest url]);
966 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
971 } else if (objectRequest.responseStatusCode == 409) {
972 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
973 if (iteration == 0) {
974 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
975 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
976 [alert setMessageText:@"Upload Timeout"];
977 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
978 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
979 [alert addButtonWithTitle:@"OK"];
983 NSLog(@"object is missing hashes: %@", [objectRequest url]);
984 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
985 withMissingHashesResponse:[objectRequest responseString]];
986 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
987 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
988 objectName:@".upload"
989 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
990 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
991 missingBlockIndex:missingBlockIndex
992 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
993 newObjectRequest.delegate = self;
994 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
995 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
996 newObjectRequest.userInfo = objectRequest.userInfo;
997 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
998 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
999 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1000 [newObjectRequest startAsynchronous];
1002 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1006 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
1007 NSLog(@"Upload using hashmap failed");
1008 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1011 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1012 NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
1013 if (objectRequest.responseStatusCode == 201) {
1014 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1015 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1016 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1017 if (missingBlockIndex == NSNotFound) {
1018 NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
1019 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1020 objectName:[objectRequest.userInfo objectForKey:@"objectName"]
1021 contentType:[objectRequest.userInfo objectForKey:@"contentType"]
1022 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1023 blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
1024 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1027 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1028 newObjectRequest.delegate = self;
1029 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1030 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1031 newObjectRequest.userInfo = objectRequest.userInfo;
1032 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1033 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1034 [newObjectRequest startAsynchronous];
1036 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1037 objectName:@".upload"
1038 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1039 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1040 missingBlockIndex:missingBlockIndex
1041 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1042 newObjectRequest.delegate = self;
1043 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1044 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1045 newObjectRequest.userInfo = objectRequest.userInfo;
1046 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1047 [newObjectRequest startAsynchronous];
1050 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1054 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
1055 NSLog(@"Upload of missing block failed");
1056 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1059 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1060 NSLog(@"Move object completed: %@", [objectRequest url]);
1061 if (objectRequest.responseStatusCode == 201) {
1062 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1063 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1068 if (!node || !dropNode)
1071 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1075 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1076 NSLog(@"Move object failed");
1077 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1080 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1081 NSLog(@"Copy object completed: %@", [objectRequest url]);
1082 if (objectRequest.responseStatusCode == 201) {
1083 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1089 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1093 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1094 NSLog(@"Copy object failed");
1095 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1099 #pragma mark NSSplitViewDelegate
1101 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1102 if (splitView == verticalSplitView)
1105 return ([horizontalSplitView bounds].size.height - 87);
1108 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1109 if (splitView == verticalSplitView)
1112 return ([horizontalSplitView bounds].size.height - 87);
1115 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1116 if (splitView == verticalSplitView) {
1117 if (proposedPosition < 120)
1119 else if (proposedPosition > 220)
1122 return proposedPosition;
1124 return ([horizontalSplitView bounds].size.height - 87);
1129 #pragma mark NSOutlineViewDataSource
1131 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1134 if (item == containersNode)
1135 return containersNodeChildren.count;
1136 if (item == sharedNode)
1141 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1143 return (!index ? containersNode : sharedNode);
1144 if (item == sharedNode)
1145 return (!index ? mySharedNode : othersSharedNode);
1146 return [containersNodeChildren objectAtIndex:index];
1149 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1150 if ((item == containersNode) || (item == sharedNode))
1155 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1156 PithosNode *node = (PithosNode *)item;
1161 #pragma mark NSOutlineViewDelegate
1163 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1164 if ((item == containersNode) || (item == sharedNode))
1169 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1170 if ((item == containersNode) || (item == sharedNode))
1175 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1176 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1179 [browser loadColumnZero];
1185 #pragma mark NSMenuDelegate
1187 - (void)menuNeedsUpdate:(NSMenu *)menu {
1188 NSInteger column = [browser clickedColumn];
1189 NSInteger row = [browser clickedRow];
1190 [menu removeAllItems];
1191 NSMenuItem *menuItem;
1192 NSString *menuItemTitle;
1193 if ((column == -1) || (row == -1)) {
1194 // General context menu
1195 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1196 PithosNode *menuNode;
1197 if ([menuNodesIndexPaths count] == 0) {
1198 menuNode = [browser parentForItemsInColumn:0];
1199 } else if (([menuNodesIndexPaths count] != 1) ||
1200 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1201 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1203 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1205 if (([menuNode class] == [PithosAccountNode class]) || ([menuNode class] == [PithosSharingAccountsNode class]))
1207 BOOL shared = menuNode.shared;
1208 BOOL sharingAccount = (menuNode.sharingAccount != nil);
1210 if (!shared && !sharingAccount) {
1212 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1213 [menuItem setRepresentedObject:menuNode];
1214 [menu addItem:menuItem];
1215 [menu addItem:[NSMenuItem separatorItem]];
1219 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1220 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1221 [menu addItem:menuItem];
1223 if (!shared && !sharingAccount) {
1225 if (clipboardNodes) {
1226 NSUInteger clipboardNodesCount = [clipboardNodes count];
1227 if (clipboardNodesCount == 0) {
1228 self.clipboardNodes = nil;
1229 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1230 if (clipboardNodesCount == 1)
1231 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1233 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1234 [menu addItem:[NSMenuItem separatorItem]];
1235 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1236 [menuItem setRepresentedObject:menuNode];
1237 [menu addItem:menuItem];
1242 // Node context menu
1243 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1244 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1245 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1246 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1247 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1248 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1251 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1253 NSUInteger menuNodesCount = [menuNodes count];
1254 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1255 BOOL shared = firstMenuNode.shared;
1256 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1258 if (!shared && !sharingAccount) {
1259 // Move to Trash (pithos container only)
1261 if ([rootNode class] == [PithosContainerNode class]) {
1262 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1263 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1264 [menuItem setRepresentedObject:menuNodes];
1265 [menu addItem:menuItem];
1267 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1268 [menuItem setRepresentedObject:menuNodes];
1269 [menu addItem:menuItem];
1270 [menu addItem:[NSMenuItem separatorItem]];
1274 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1276 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1277 [menuItem setRepresentedObject:menuNodes];
1278 [menu addItem:menuItem];
1280 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1281 [menu addItem:[NSMenuItem separatorItem]];
1284 if (!shared && !sharingAccount) {
1287 if (menuNodesCount == 1)
1288 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1290 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1291 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1292 [menuItem setRepresentedObject:menuNodes];
1293 [menu addItem:menuItem];
1296 if ((!shared && !sharingAccount) ||
1297 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1299 if (menuNodesCount == 1)
1300 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1302 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1303 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1304 [menuItem setRepresentedObject:menuNodes];
1305 [menu addItem:menuItem];
1308 if (!shared && !sharingAccount) {
1310 if (menuNodesCount == 1) {
1311 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1312 if (([menuNode class] == [PithosSubdirNode class]) &&
1313 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1314 if (clipboardNodes) {
1315 NSUInteger clipboardNodesCount = [clipboardNodes count];
1316 if (clipboardNodesCount == 0) {
1317 self.clipboardNodes = nil;
1318 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1319 if (clipboardNodesCount == 1)
1320 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1322 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1323 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1324 [menuItem setRepresentedObject:menuNode];
1325 [menu addItem:menuItem];
1335 #pragma mark Menu Actions
1337 - (void)menuNewFolder:(NSMenuItem *)sender {
1338 PithosNode *node = (PithosNode *)[sender representedObject];
1339 if ([node class] == [PithosContainerNode class]) {
1340 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1341 dispatch_async(queue, ^{
1342 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1343 subdirName:@"untitled folder"];
1344 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1345 objectName:safeObjectName
1347 contentType:@"application/directory"
1349 contentDisposition:nil
1352 isPublic:ASIPithosObjectRequestPublicIgnore
1354 data:[NSData data]];
1355 objectRequest.delegate = self;
1356 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1357 objectRequest.didFailSelector = @selector(newFolderFailed:);
1358 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1361 [objectRequest startAsynchronous];
1363 } else if (([node class] == [PithosSubdirNode class]) &&
1364 (node.pithosObject.subdir ||
1365 ![node.pithosObject.name hasSuffix:@"/"])) {
1366 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1367 dispatch_async(queue, ^{
1368 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1369 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1370 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1371 objectName:safeObjectName
1373 contentType:@"application/directory"
1375 contentDisposition:nil
1378 isPublic:ASIPithosObjectRequestPublicIgnore
1380 data:[NSData data]];
1381 objectRequest.delegate = self;
1382 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1383 objectRequest.didFailSelector = @selector(newFolderFailed:);
1384 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1387 [objectRequest startAsynchronous];
1392 - (void)menuGetInfo:(NSMenuItem *)sender {
1393 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1394 [node showPithosNodeInfo:sender];
1398 - (void)menuDelete:(NSMenuItem *)sender {
1399 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1400 if (([node class] == [PithosObjectNode class]) ||
1401 (([node class] == [PithosSubdirNode class]) &&
1402 !node.pithosObject.subdir &&
1403 [node.pithosObject.name hasSuffix:@"/"])) {
1404 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1405 objectName:node.pithosObject.name];
1406 objectRequest.delegate = self;
1407 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1408 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1409 [objectRequest startAsynchronous];
1410 } else if ([node class] == [PithosSubdirNode class]) {
1411 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1412 dispatch_async(queue, ^{
1413 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1414 objectName:node.pithosObject.name];
1415 if (objectRequests) {
1416 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1417 objectRequest.delegate = self;
1418 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1419 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1420 [objectRequest startAsynchronous];
1428 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1429 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1430 if (([node class] == [PithosObjectNode class]) ||
1431 (([node class] == [PithosSubdirNode class]) &&
1432 !node.pithosObject.subdir &&
1433 [node.pithosObject.name hasSuffix:@"/"])) {
1434 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1435 dispatch_async(queue, ^{
1436 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1437 objectName:node.pithosObject.name];
1438 if (safeObjectName) {
1439 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1440 objectName:node.pithosObject.name
1441 destinationContainerName:@"trash"
1442 destinationObjectName:safeObjectName
1444 if (objectRequest) {
1445 objectRequest.delegate = self;
1446 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1447 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1448 [objectRequest startAsynchronous];
1452 } else if ([node class] == [PithosSubdirNode class]) {
1453 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1454 dispatch_async(queue, ^{
1455 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1456 subdirName:node.pithosObject.name];
1457 if (safeObjectName) {
1458 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1459 objectName:node.pithosObject.name
1460 destinationContainerName:@"trash"
1461 destinationObjectName:safeObjectName
1463 if (objectRequests) {
1464 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1465 objectRequest.delegate = self;
1466 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1467 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1468 [objectRequest startAsynchronous];
1477 - (void)menuCut:(NSMenuItem *)sender {
1478 self.clipboardNodes = (NSArray *)[sender representedObject];
1479 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1480 self.clipboardCopy = NO;
1483 - (void)menuCopy:(NSMenuItem *)sender {
1484 self.clipboardNodes = (NSArray *)[sender representedObject];
1485 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1486 self.clipboardCopy = YES;
1489 - (void)menuPaste:(NSMenuItem *)sender {
1490 if (!clipboardNodes || ![clipboardNodes count])
1492 PithosNode *dropNode = (PithosNode *)[sender representedObject];
1493 if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) ||
1494 (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"]))
1497 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1498 NSString *objectNamePrefix;
1499 if ([dropNode class] == [PithosSubdirNode class])
1500 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1502 objectNamePrefix = [NSString string];
1504 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1505 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1506 self.clipboardNodes = nil;
1507 self.clipboardParentNode = nil;
1508 for (PithosNode *node in localClipboardNodes) {
1509 if (([node class] == [PithosObjectNode class]) ||
1510 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1511 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1512 dispatch_async(queue, ^{
1513 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1514 if ([node.pithosObject.name hasSuffix:@"/"])
1515 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1516 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1517 objectName:node.pithosObject.name
1518 destinationContainerName:containerName
1519 destinationObjectName:destinationObjectName
1521 if (objectRequest) {
1522 objectRequest.delegate = self;
1523 objectRequest.didFinishSelector = @selector(moveFinished:);
1524 objectRequest.didFailSelector = @selector(moveFailed:);
1525 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1526 node.parent, @"node",
1527 dropNode, @"dropNode",
1529 [objectRequest startAsynchronous];
1532 } else if ([node class] == [PithosSubdirNode class]) {
1533 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1534 dispatch_async(queue, ^{
1535 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1536 if (node.pithosObject.subdir)
1537 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1538 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1539 objectName:node.pithosObject.name
1540 destinationContainerName:containerName
1541 destinationObjectName:destinationObjectName
1543 if (objectRequests) {
1544 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1545 objectRequest.delegate = self;
1546 objectRequest.didFinishSelector = @selector(moveFinished:);
1547 objectRequest.didFailSelector = @selector(moveFailed:);
1548 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1549 node.parent, @"node",
1550 dropNode, @"dropNode",
1552 [objectRequest startAsynchronous];
1559 for (PithosNode *node in localClipboardNodes) {
1560 if (([node class] == [PithosObjectNode class]) ||
1561 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1562 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1563 dispatch_async(queue, ^{
1564 NSString *destinationObjectName;
1565 if (![dropNode isEqualTo:node.parent]) {
1566 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1567 if ([node.pithosObject.name hasSuffix:@"/"])
1568 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1570 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
1571 objectName:node.pithosObject.name];
1573 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1574 objectName:node.pithosObject.name
1575 destinationContainerName:containerName
1576 destinationObjectName:destinationObjectName
1578 sharingAccount:node.sharingAccount];
1579 if (objectRequest) {
1580 objectRequest.delegate = self;
1581 objectRequest.didFinishSelector = @selector(copyFinished:);
1582 objectRequest.didFailSelector = @selector(copyFailed:);
1583 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1584 dropNode, @"dropNode",
1586 [objectRequest startAsynchronous];
1589 } else if ([node class] == [PithosSubdirNode class]) {
1590 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1591 dispatch_async(queue, ^{
1592 NSString *destinationObjectName;
1593 if (![dropNode isEqualTo:node.parent]) {
1594 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1595 if (node.pithosObject.subdir)
1596 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1598 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
1599 subdirName:node.pithosObject.name];
1601 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1602 objectName:node.pithosObject.name
1603 destinationContainerName:containerName
1604 destinationObjectName:destinationObjectName
1606 sharingAccount:node.sharingAccount];
1607 if (objectRequests) {
1608 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1609 objectRequest.delegate = self;
1610 objectRequest.didFinishSelector = @selector(copyFinished:);
1611 objectRequest.didFailSelector = @selector(copyFailed:);
1612 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1613 dropNode, @"dropNode",
1615 [objectRequest startAsynchronous];
1625 #pragma mark Menu Actions ASIHTTPRequestDelegate
1627 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1628 if (objectRequest.responseStatusCode == 201) {
1629 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1630 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1636 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1640 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1641 NSLog(@"Creation of new application/directory object failed");
1642 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1645 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1646 if (objectRequest.responseStatusCode == 204) {
1647 NSLog(@"Object deleted: %@", [objectRequest url]);
1650 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1654 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1655 NSLog(@"Delete of object failed");
1656 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1659 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1660 if (objectRequest.responseStatusCode == 201) {
1661 NSLog(@"Object moved: %@", [objectRequest url]);
1664 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1668 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1669 NSLog(@"Move of object failed");
1670 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];