// // PithosBrowserController.m // pithos-macos // // Copyright 2011 GRNET S.A. All rights reserved. // // Redistribution and use in source and binary forms, with or // without modification, are permitted provided that the following // conditions are met: // // 1. Redistributions of source code must retain the above // copyright notice, this list of conditions and the following // disclaimer. // // 2. Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials // provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. // // The views and conclusions contained in the software and // documentation are those of the authors and should not be // interpreted as representing official policies, either expressed // or implied, of GRNET S.A. #import "PithosBrowserController.h" #import "PithosNode.h" #import "PithosAccountNode.h" #import "PithosContainerNode.h" #import "PithosSubdirNode.h" #import "PithosObjectNode.h" #import "PithosEmptyNode.h" #import "ImageAndTextCell.h" #import "FileSystemBrowserCell.h" #import "ASIPithosRequest.h" #import "ASIPithosContainerRequest.h" #import "ASIPithosObjectRequest.h" #import "ASIPithosContainer.h" #import "ASIPithosObject.h" #import "PithosFileUtilities.h" @interface PithosBrowserCell : FileSystemBrowserCell {} @end @implementation PithosBrowserCell - (id)init { if ((self = [super init])) { [self setLineBreakMode:NSLineBreakByTruncatingMiddle]; [self setEditable:YES]; } return self; } - (void)setObjectValue:(id)object { if ([object isKindOfClass:[PithosNode class]]) { PithosNode *node = (PithosNode *)object; [self setStringValue:node.displayName]; [self setImage:node.icon]; } else { [super setObjectValue:object]; } } @end @interface PithosOutlineViewCell : ImageAndTextCell {} @end @implementation PithosOutlineViewCell - (void)setObjectValue:(id)object { if ([object isKindOfClass:[PithosNode class]]) { PithosNode *node = (PithosNode *)object; [self setStringValue:node.displayName]; [self setImage:node.icon]; [self setEditable:NO]; } else { [super setObjectValue:object]; } } @end @interface PithosBrowserController (Private) {} - (void)resetContainers:(NSNotification *)notification; - (void)getInfo:(NSMenuItem *)sender; - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest; - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest; - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest; - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest; - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest; - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest; @end @implementation PithosBrowserController @synthesize outlineViewDataSourceArray, verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, draggedNodes, draggedParentNode; #pragma mark - #pragma Object Lifecycle - (id)init { return [super initWithWindowNibName:@"PithosBrowserController"]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [draggedParentNode release]; [draggedNodes release]; [browserMenu release]; [sharedPreviewController release]; [outlineViewDataSourceArray release]; [accountNode release]; [rootNode release]; [super dealloc]; } - (void)awakeFromNib { [super awakeFromNib]; [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]]; [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES]; [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; [browser setCellClass:[PithosBrowserCell class]]; browserMenu = [[NSMenu alloc] init]; [browserMenu setDelegate:self]; [browser setMenu:browserMenu]; } - (void)resetContainers:(NSNotification *)notification { rootNode = nil; [browser loadColumnZero]; self.outlineViewDataSourceArray = nil; // Create the outlineView tree // CONTAINERS NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject: [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]]; // SHARED NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject: [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]]; // SHARED/my shared [[sharedTreeNode mutableChildNodes] addObject: [NSTreeNode treeNodeWithRepresentedObject: [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared" icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)] ] autorelease]]]; // SHARED/others shared [[sharedTreeNode mutableChildNodes] addObject: [NSTreeNode treeNodeWithRepresentedObject: [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared" icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)] ] autorelease]]]; self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil]; // Expand the folder outline view [outlineView expandItem:nil expandChildren:YES]; [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO]; // Create accountNode and trigger a refresh accountNode = [[PithosAccountNode alloc] init]; accountNode.children; } - (void)windowDidLoad { [super windowDidLoad]; [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]]; // Register for updates [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosNodeChildrenUpdated:) name:@"PithosContainerNodeChildrenUpdated" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosNodeChildrenUpdated:) name:@"PithosSubdirNodeChildrenUpdated" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(pithosAccountNodeChildrenUpdated:) name:@"PithosAccountNodeChildrenUpdated" object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resetContainers:) name:@"PithosAuthenticationCredentialsUpdated" object:nil]; } #pragma mark - #pragma Observers - (void)pithosNodeChildrenUpdated:(NSNotification *)notification { PithosNode *node = (PithosNode *)[notification object]; NSLog(@"pithosNodeChildrenUpdated:%@", node.url); NSInteger lastColumn = [browser lastColumn]; for (NSInteger column = lastColumn; column >= 0; column--) { if ([[browser parentForItemsInColumn:column] isEqualTo:node]) { [browser reloadColumn:column]; // The following code is unnecessary since the pithosObject is set in the PithosNode in each refresh // Furthermore it caused problems on delete, because a non-existing parent was asked for //if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) { // // This reloads the preview column // [browser setLastColumn:column]; // [browser addColumn]; //} return; } } } - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification { BOOL containerPithosFound = NO; BOOL containerTrashFound = NO; NSMutableArray *containersTreeNodeChildren = [NSMutableArray array]; for (PithosContainerNode *containerNode in accountNode.children) { if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) { containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]; [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0]; containerPithosFound = YES; } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) { containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]; NSUInteger insertIndex = 1; if (!containerPithosFound) insertIndex = 0; [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex]; containerTrashFound = YES; } else { [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]]; } } BOOL refreshAccountNode = NO; if (!containerPithosFound) { // create pithos ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"]; [containerRequest startSynchronous]; if ([containerRequest error]) { [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest]; } else { refreshAccountNode = YES; } } if (!containerTrashFound) { // create trash ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"]; [containerRequest startSynchronous]; if ([containerRequest error]) { [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest]; } else { refreshAccountNode = YES; } } if (refreshAccountNode) { [accountNode invalidateChildren]; accountNode.children; } else { [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren]; self.outlineViewDataSourceArray = outlineViewDataSourceArray; // Expand the folder outline view [outlineView expandItem:nil expandChildren:YES]; [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO]; [self refresh:nil]; } } #pragma mark - #pragma Actions - (IBAction)refresh:(id)sender { for (NSInteger column = [browser lastColumn]; column >= 0; column--) { [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren]; } [browser validateVisibleColumns]; } #pragma mark - #pragma NSBrowserDelegate - (id)rootItemForBrowser:(NSBrowser *)browser { return rootNode; } - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item { PithosNode *node = (PithosNode *)item; return node.children.count; } - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item { PithosNode *node = (PithosNode *)item; return [node.children objectAtIndex:index]; } - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item { PithosNode *node = (PithosNode *)item; return node.isLeafItem; } - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item { PithosNode *node = (PithosNode *)item; return node; } - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item { if (sharedPreviewController == nil) sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]]; return sharedPreviewController; } //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth { // if (!forUserResize) { // id item = [browser parentForItemsInColumn:columnIndex]; // if ([self browser:browser isLeafItem:item]) { // suggestedWidth = 200; // } // } // return suggestedWidth; //} - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column { return NO; } #pragma mark Editing - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item { return YES; } - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item { PithosNode *node = (PithosNode *)item; NSString *newName = (NSString *)object; NSUInteger newNameLength = [newName length]; NSRange firstSlashRange = [newName rangeOfString:@"/"]; if ((newNameLength == 0) || ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) || ([newName isEqualToString:node.displayName])) { return; } if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; if ([newName hasSuffix:@"/"]) destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; NSError *error = nil; BOOL isDirectory; if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name objectName:destinationObjectName error:&error isDirectory:&isDirectory]) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Name Taken"]; [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return; } else if (error) { return; } ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:node.pithosContainer.name destinationObjectName:destinationObjectName checkIfExists:NO]; if (objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(moveFinished:); objectRequest.didFailSelector = @selector(moveFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: node.parent, @"node", nil]; [objectRequest startAsynchronous]; } }); } else if ([node class] == [PithosSubdirNode class]) { if (firstSlashRange.length == 1) return; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName]; NSError *error = nil; BOOL isDirectory; if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name objectName:destinationObjectName error:&error isDirectory:&isDirectory]) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Name Taken"]; [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return; } else if (error) { return; } if (node.pithosObject.subdir) destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:node.pithosContainer.name destinationObjectName:destinationObjectName checkIfExists:NO]; if (objectRequests) { for (ASIPithosObjectRequest *objectRequest in objectRequests) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(moveFinished:); objectRequest.didFailSelector = @selector(moveFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: node.parent, @"node", nil]; [objectRequest startAsynchronous]; } } }); } } #pragma mark Drag and Drop source - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column toPasteboard:(NSPasteboard *)pasteboard { NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){ PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]]; [propertyList addObject:[node.pithosObject.name pathExtension]]; [nodes addObject:node]; }]; [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self]; [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType]; self.draggedNodes = nodes; self.draggedParentNode = [browser parentForItemsInColumn:column]; return YES; } - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column { NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]]; NSIndexPath *baseIndexPath = [browser indexPathForColumn:column]; [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){ PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]]; // If the node is a subdir ask if the whole tree should be downloaded if ([node class] == [PithosSubdirNode class]) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Download directory"]; [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertFirstButtonReturn) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name toDirectory:[dropDestination path] checkIfExists:YES]; if (objectRequests) { for (ASIPithosObjectRequest *objectRequest in objectRequests) { [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(downloadObjectFinished:); objectRequest.didFailSelector = @selector(downloadObjectFailed:); [objectRequest startAsynchronous]; } } }); } } else { ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name toDirectory:[dropDestination path] checkIfExists:YES]; if (objectRequest) { [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(downloadObjectFinished:); objectRequest.didFailSelector = @selector(downloadObjectFailed:); [objectRequest startAsynchronous]; } } }]; return names; } #pragma mark Drag and Drop destination - (NSDragOperation)browser:aBrowser validateDrop:(id)info proposedRow:(NSInteger *)row column:(NSInteger *)column dropOperation:(NSBrowserDropOperation *)dropOperation { NSDragOperation result = NSDragOperationNone; if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) { // For a drop above, the drop is redirected to the parent item if (*dropOperation == NSBrowserDropAbove) *row = -1; // Only allow dropping in folders if (*column != -1) { if (*row != -1) { // Check if the node is not a folder and if so redirect to the parent item if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class]) *row = -1; } *dropOperation = NSBrowserDropOn; result = NSDragOperationCopy; } } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { // For a drop above, the drop is redirected to the parent item if (*dropOperation == NSBrowserDropAbove) *row = -1; // Only allow dropping in folders if (*column != -1) { PithosNode *dropNode; if (*row != -1) { // Check if the node is not a folder and if so redirect to the parent item dropNode = [browser itemAtRow:*row inColumn:*column]; if ([dropNode class] != [PithosSubdirNode class]) *row = -1; } if (*row == -1) dropNode = [browser parentForItemsInColumn:*column]; if ([info draggingSourceOperationMask] & NSDragOperationMove) { // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove if ((([dropNode class] == [PithosContainerNode class]) || dropNode.pithosObject.subdir || ![dropNode.pithosObject.name hasSuffix:@"/"]) && ![dropNode isEqualTo:draggedParentNode]) { // ![dropNode isEqualTo:draggedParentNode] && // ![draggedNodes containsObject:dropNode]) { result = NSDragOperationMove; } } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) { // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy if (([dropNode class] == [PithosContainerNode class]) || dropNode.pithosObject.subdir || ![dropNode.pithosObject.name hasSuffix:@"/"]) { result = NSDragOperationCopy; } } } } return result; } - (BOOL)browser:(NSBrowser *)aBrowser acceptDrop:(id)info atRow:(NSInteger)row column:(NSInteger)column dropOperation:(NSBrowserDropOperation)dropOperation { if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) { NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType]; NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames); if ((column != -1) && (filenames != nil)) { PithosNode *node = nil; if (row != -1) node = [browser itemAtRow:row inColumn:column]; else node = [browser parentForItemsInColumn:column]; NSLog(@"drag in node: %@", node.url); if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class])) return NO; NSFileManager *defaultManager = [NSFileManager defaultManager]; NSString *containerName = [NSString stringWithString:node.pithosContainer.name]; NSString *objectNamePrefix; if ([node class] == [PithosSubdirNode class]) objectNamePrefix = [NSString stringWithString:node.pithosObject.name]; else objectNamePrefix = [NSString string]; NSUInteger blockSize = node.pithosContainer.blockSize; NSString *blockHash = node.pithosContainer.blockHash; for (NSString *filePath in filenames) { BOOL isDirectory; if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) { if (!isDirectory) { // Upload file NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSError *error = nil; NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error]; if (contentType == nil) contentType = @"application/octet-stream"; if (error) NSLog(@"contentType detection error: %@", error); NSArray *hashes = nil; ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName objectName:objectName contentType:contentType blockSize:blockSize blockHash:blockHash forFile:filePath checkIfExists:YES hashes:&hashes]; if (objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:); objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"containerName", objectName, @"objectName", contentType, @"contentType", [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", blockHash, @"blockHash", filePath, @"filePath", hashes, @"hashes", node, @"node", [NSNumber numberWithUnsignedInteger:10], @"iteration", nil]; [objectRequest startAsynchronous]; } }); } else { // Upload directory, confirm first NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Upload directory"]; [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]]; [alert addButtonWithTitle:@"OK"]; [alert addButtonWithTitle:@"Cancel"]; NSInteger choice = [alert runModal]; if (choice == NSAlertFirstButtonReturn) { NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSMutableArray *objectNames = nil; NSMutableArray *contentTypes = nil; NSMutableArray *filePaths = nil; NSMutableArray *hashesArrays = nil; NSMutableArray *directoryObjectRequests = nil; NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName objectName:objectName blockSize:blockSize blockHash:blockHash forDirectory:filePath checkIfExists:YES objectNames:&objectNames contentTypes:&contentTypes filePaths:&filePaths hashesArrays:&hashesArrays directoryObjectRequests:&directoryObjectRequests]; for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:); objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:); [objectRequest startAsynchronous]; } if (objectRequests) { for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) { ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:); objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: containerName, @"containerName", [objectNames objectAtIndex:i], @"objectName", [contentTypes objectAtIndex:i], @"contentType", [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize", blockHash, @"blockHash", [filePaths objectAtIndex:i], @"filePath", [hashesArrays objectAtIndex:i], @"hashes", [NSNumber numberWithUnsignedInteger:10], @"iteration", nil]; [objectRequest startAsynchronous]; } } }); } } } } return YES; } } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) { NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes); if ((column != -1) && (draggedNodes != nil)) { PithosNode *dropNode = nil; if (row != -1) dropNode = [browser itemAtRow:row inColumn:column]; else dropNode = [browser parentForItemsInColumn:column]; NSLog(@"drag local node: %@", dropNode.url); if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) return NO; NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name]; NSString *objectNamePrefix; if ([dropNode class] == [PithosSubdirNode class]) objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name]; else objectNamePrefix = [NSString string]; if ([info draggingSourceOperationMask] & NSDragOperationMove) { for (PithosNode *node in draggedNodes) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName]; if ([node.pithosObject.name hasSuffix:@"/"]) destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:containerName destinationObjectName:destinationObjectName checkIfExists:YES]; if (objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(moveFinished:); objectRequest.didFailSelector = @selector(moveFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: node.parent, @"node", dropNode, @"dropNode", nil]; [objectRequest startAsynchronous]; } }); } else if ([node class] == [PithosSubdirNode class]) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName]; if (node.pithosObject.subdir) destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:containerName destinationObjectName:destinationObjectName checkIfExists:YES]; if (objectRequests) { for (ASIPithosObjectRequest *objectRequest in objectRequests) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(moveFinished:); objectRequest.didFailSelector = @selector(moveFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: node.parent, @"node", dropNode, @"dropNode", nil]; [objectRequest startAsynchronous]; } } }); } } } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) { for (PithosNode *node in draggedNodes) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *destinationObjectName; if (![dropNode isEqualTo:node.parent]) { destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName]; if ([node.pithosObject.name hasSuffix:@"/"]) destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; } else { destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName objectName:node.pithosObject.name]; } ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:containerName destinationObjectName:destinationObjectName checkIfExists:YES]; if (objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(copyFinished:); objectRequest.didFailSelector = @selector(copyFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: dropNode, @"dropNode", nil]; [objectRequest startAsynchronous]; } }); } else if ([node class] == [PithosSubdirNode class]) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *destinationObjectName; if (![dropNode isEqualTo:node.parent]) { destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName]; if (node.pithosObject.subdir) destinationObjectName = [destinationObjectName stringByAppendingString:@"/"]; } else { destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName subdirName:node.pithosObject.name]; } NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:containerName destinationObjectName:destinationObjectName checkIfExists:YES]; if (objectRequests) { for (ASIPithosObjectRequest *objectRequest in objectRequests) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(copyFinished:); objectRequest.didFailSelector = @selector(copyFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: dropNode, @"dropNode", nil]; [objectRequest startAsynchronous]; } } }); } } } return YES; } } return NO; } #pragma mark - #pragma mark ASIHTTPRequestDelegate - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Download completed: %@", [objectRequest url]); if (objectRequest.responseStatusCode == 200) { if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) { NSLog(@"Downloaded 0 bytes"); NSFileManager *defaultManager = [NSFileManager defaultManager]; NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"]; if (![defaultManager fileExistsAtPath:filePath]) { if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Create File Error"]; [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; } } } } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Download failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Upload directory object completed: %@", [objectRequest url]); if (objectRequest.responseStatusCode == 201) { NSLog(@"Directory object created: %@", [objectRequest url]); [self refresh:nil]; } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Upload directory object failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Upload using hashmap completed: %@", [objectRequest url]); if (objectRequest.responseStatusCode == 201) { NSLog(@"Object created: %@", [objectRequest url]); PithosNode *node = [objectRequest.userInfo objectForKey:@"node"]; if (node) { [node invalidateChildren]; node.children; } else { [self refresh:nil]; } } else if (objectRequest.responseStatusCode == 409) { NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1; if (iteration == 0) { NSLog(@"Upload iteration limit reached: %@", [objectRequest url]); NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:@"Upload Timeout"]; [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'", [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]]; [alert addButtonWithTitle:@"OK"]; [alert runModal]; return; } NSLog(@"object is missing hashes: %@", [objectRequest url]); NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"] withMissingHashesResponse:[objectRequest responseString]]; NSUInteger missingBlockIndex = [missingBlocks firstIndex]; ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] objectName:@".upload" blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] forFile:[objectRequest.userInfo objectForKey:@"filePath"] missingBlockIndex:missingBlockIndex]; newObjectRequest.delegate = self; newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:); newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:); newObjectRequest.userInfo = objectRequest.userInfo; [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"]; [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"]; [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"]; [newObjectRequest startAsynchronous]; } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Upload using hashmap failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Upload of missing block completed: %@", [objectRequest url]); if (objectRequest.responseStatusCode == 201) { NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"]; NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue]; missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex]; if (missingBlockIndex == NSNotFound) { NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"]; ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] objectName:[objectRequest.userInfo objectForKey:@"objectName"] contentType:[objectRequest.userInfo objectForKey:@"contentType"] blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] blockHash:[objectRequest.userInfo objectForKey:@"blockHash"] forFile:[objectRequest.userInfo objectForKey:@"filePath"] checkIfExists:NO hashes:&hashes]; newObjectRequest.delegate = self; newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:); newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:); newObjectRequest.userInfo = objectRequest.userInfo; [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"]; [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"]; [newObjectRequest startAsynchronous]; } else { ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"] objectName:@".upload" blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue] forFile:[objectRequest.userInfo objectForKey:@"filePath"] missingBlockIndex:missingBlockIndex]; newObjectRequest.delegate = self; newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:); newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:); newObjectRequest.userInfo = objectRequest.userInfo; [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"]; [newObjectRequest startAsynchronous]; } } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Upload of missing block failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Move object completed: %@", [objectRequest url]); if (objectRequest.responseStatusCode == 201) { PithosNode *node = [objectRequest.userInfo objectForKey:@"node"]; PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"]; if (node) { [node invalidateChildren]; node.children; } if (dropNode) { [dropNode invalidateChildren]; dropNode.children; } if (!node || !dropNode) [self refresh:nil]; } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Move object failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Copy object completed: %@", [objectRequest url]); if (objectRequest.responseStatusCode == 201) { PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"]; if (dropNode) { [dropNode invalidateChildren]; dropNode.children; } else { [self refresh:nil]; } } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Copy object failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } #pragma mark - #pragma mark NSSplitViewDelegate - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex { if (splitView == verticalSplitView) return 120; else return ([horizontalSplitView bounds].size.height - 87); } - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex { if (splitView == verticalSplitView) return 220; else return ([horizontalSplitView bounds].size.height - 87); } - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex { if (splitView == verticalSplitView) { if (proposedPosition < 120) return 120; else if (proposedPosition > 220) return 220; else return proposedPosition; } else { return ([horizontalSplitView bounds].size.height - 87); } } #pragma mark - #pragma mark NSOutlineViewDelegate - (BOOL)outlineView:outlineView shouldSelectItem:(id)item { return ([[item representedObject] isLeaf]); } - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item { return (![[item representedObject] isLeaf]); } - (void)outlineViewSelectionDidChange:(NSNotification *)notification { PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject]; if (node) { rootNode = node; [browser loadColumnZero]; [self refresh:nil]; } } #pragma mark - #pragma mark NSMenuDelegate - (void)menuNeedsUpdate:(NSMenu *)menu { NSInteger column = [browser clickedColumn]; NSInteger row = [browser clickedRow]; [menu removeAllItems]; NSMenuItem *menuItem; if ((column == -1) || (row == -1)) { // General context menu NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; PithosNode *menuNode; if ([menuNodesIndexPaths count] == 0) { menuNode = [browser parentForItemsInColumn:0]; } else if (([menuNodesIndexPaths count] != 1) || ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) { menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)]; } else { menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]]; } NSArray *menuNodes = [NSArray arrayWithObject:menuNode]; // New Folder menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(newFolder:) keyEquivalent:@""] autorelease]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; [menu addItem:[NSMenuItem separatorItem]]; // Get Info menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; } else { // Node context menu NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row]; NSArray *menuNodesIndexPaths = [browser selectionIndexPaths]; NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]]; if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) { for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) { [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]]; } } else { [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]]; } // Move to Trash (pithos container only) // Delete if ([rootNode class] == [PithosContainerNode class]) { if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) { menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; } menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; [menu addItem:[NSMenuItem separatorItem]]; } // Get Info menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease]; [menuItem setRepresentedObject:menuNodes]; [menu addItem:menuItem]; } } #pragma mark - #pragma mark Menu Actions - (void)newFolder:(NSMenuItem *)sender { for (PithosNode *node in ((NSArray *)[sender representedObject])) { if ([node class] == [PithosContainerNode class]) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name subdirName:@"untitled folder"]; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name objectName:safeObjectName eTag:nil contentType:@"application/directory" contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil data:[NSData data]]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(newFolderFinished:); objectRequest.didFailSelector = @selector(newFolderFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: node, @"node", nil]; [objectRequest startAsynchronous]; }); } else if (([node class] == [PithosSubdirNode class]) && (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]]; ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name objectName:safeObjectName eTag:nil contentType:@"application/directory" contentEncoding:nil contentDisposition:nil manifest:nil sharing:nil isPublic:ASIPithosObjectRequestPublicIgnore metadata:nil data:[NSData data]]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(newFolderFinished:); objectRequest.didFailSelector = @selector(newFolderFailed:); objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: node, @"node", nil]; [objectRequest startAsynchronous]; }); } } } - (void)getInfo:(NSMenuItem *)sender { for (PithosNode *node in ((NSArray *)[sender representedObject])) { [node showPithosNodeInfo:sender]; } } - (void)deleteObject:(NSMenuItem *)sender { for (PithosNode *node in ((NSArray *)[sender representedObject])) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name]; objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(deleteObjectFinished:); objectRequest.didFailSelector = @selector(deleteObjectFailed:); [objectRequest startAsynchronous]; } else if ([node class] == [PithosSubdirNode class]) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name]; if (objectRequests) { for (ASIPithosObjectRequest *objectRequest in objectRequests) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(deleteObjectFinished:); objectRequest.didFailSelector = @selector(deleteObjectFailed:); [objectRequest startAsynchronous]; } } }); } } } - (void)moveToTrash:(NSMenuItem *)sender { for (PithosNode *node in ((NSArray *)[sender representedObject])) { if (([node class] == [PithosObjectNode class]) || (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash" objectName:node.pithosObject.name]; if (safeObjectName) { ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:@"trash" destinationObjectName:safeObjectName checkIfExists:NO]; if (objectRequest) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(moveToTrashFinished:); objectRequest.didFailSelector = @selector(moveToTrashFailed:); [objectRequest startAsynchronous]; } } }); } else if ([node class] == [PithosSubdirNode class]) { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash" subdirName:node.pithosObject.name]; if (safeObjectName) { NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name objectName:node.pithosObject.name destinationContainerName:@"trash" destinationObjectName:safeObjectName checkIfExists:NO]; if (objectRequests) { for (ASIPithosObjectRequest *objectRequest in objectRequests) { objectRequest.delegate = self; objectRequest.didFinishSelector = @selector(moveToTrashFinished:); objectRequest.didFailSelector = @selector(moveToTrashFailed:); [objectRequest startAsynchronous]; } } } }); } } } #pragma mark - #pragma mark Menu Actions ASIHTTPRequestDelegate - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest { if (objectRequest.responseStatusCode == 201) { NSLog(@"New application/directory object created: %@", [objectRequest url]); PithosNode *node = [objectRequest.userInfo objectForKey:@"node"]; if (node) { [node invalidateChildren]; node.children; } else { [self refresh:nil]; } } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Creation of new application/directory object failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest { if (objectRequest.responseStatusCode == 204) { NSLog(@"Object deleted: %@", [objectRequest url]); [self refresh:nil]; } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Delete of object failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest { if (objectRequest.responseStatusCode == 201) { NSLog(@"Object moved: %@", [objectRequest url]); [self refresh:nil]; } else { [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest]; } } - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest { NSLog(@"Move of object failed"); [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest]; } @end