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 "PithosEmptyNode.h"
45 #import "ImageAndTextCell.h"
46 #import "FileSystemBrowserCell.h"
47 #import "ASIPithosRequest.h"
48 #import "ASIPithosContainerRequest.h"
49 #import "ASIPithosObjectRequest.h"
50 #import "ASIPithosContainer.h"
51 #import "ASIPithosObject.h"
52 #import "PithosFileUtilities.h"
54 @interface PithosBrowserCell : FileSystemBrowserCell {}
57 @implementation PithosBrowserCell
60 if ((self = [super init])) {
61 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
62 [self setEditable:YES];
67 - (void)setObjectValue:(id)object {
68 if ([object isKindOfClass:[PithosNode class]]) {
69 PithosNode *node = (PithosNode *)object;
70 [self setStringValue:node.displayName];
71 [self setImage:node.icon];
73 [super setObjectValue:object];
79 @interface PithosOutlineViewCell : ImageAndTextCell {}
82 @implementation PithosOutlineViewCell
84 - (void)setObjectValue:(id)object {
85 if ([object isKindOfClass:[PithosNode class]]) {
86 PithosNode *node = (PithosNode *)object;
87 [self setStringValue:node.displayName];
88 [self setImage:node.icon];
89 [self setEditable:NO];
91 [super setObjectValue:object];
97 @interface PithosBrowserController (Private) {}
98 - (void)resetContainers:(NSNotification *)notification;
101 @implementation PithosBrowserController
102 @synthesize outlineViewDataSourceArray, verticalSplitView, horizontalSplitView, leftTopView, leftBottomView,
103 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 [outlineViewDataSourceArray release];
123 [mySharedRootNode release];
124 [accountNode release];
129 - (void)awakeFromNib {
130 [super awakeFromNib];
132 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
133 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
134 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
136 [browser setCellClass:[PithosBrowserCell class]];
138 browserMenu = [[NSMenu alloc] init];
139 [browserMenu setDelegate:self];
140 [browser setMenu:browserMenu];
143 - (void)resetContainers:(NSNotification *)notification {
145 [browser loadColumnZero];
146 self.outlineViewDataSourceArray = nil;
148 // Create the outlineView tree
150 NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
151 [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
154 NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
155 [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
157 [[sharedTreeNode mutableChildNodes] addObject:[NSTreeNode treeNodeWithRepresentedObject:mySharedRootNode]];
158 // SHARED/others shared
159 [[sharedTreeNode mutableChildNodes] addObject:
160 [NSTreeNode treeNodeWithRepresentedObject:
161 [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
162 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
165 self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
167 // Expand the folder outline view
168 [outlineView expandItem:nil expandChildren:YES];
169 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
172 [accountNode refresh];
175 - (void)windowDidLoad {
176 [super windowDidLoad];
178 accountNode = [[PithosAccountNode alloc] init];
179 mySharedRootNode = [[PithosAccountNode alloc] init];
180 mySharedRootNode.displayName = @"my shared";
181 mySharedRootNode.shared = YES;
182 mySharedRootNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
184 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
186 // Register for updates
187 [[NSNotificationCenter defaultCenter] addObserver:self
188 selector:@selector(pithosNodeChildrenUpdated:)
189 name:@"PithosContainerNodeChildrenUpdated"
191 [[NSNotificationCenter defaultCenter] addObserver:self
192 selector:@selector(pithosNodeChildrenUpdated:)
193 name:@"PithosSubdirNodeChildrenUpdated"
195 [[NSNotificationCenter defaultCenter] addObserver:self
196 selector:@selector(pithosAccountNodeChildrenUpdated:)
197 name:@"PithosAccountNodeChildrenUpdated"
199 [[NSNotificationCenter defaultCenter] addObserver:self
200 selector:@selector(pithosNodeChildrenUpdated:)
201 name:@"PithosAccountNodeChildrenUpdated"
202 object:mySharedRootNode];
203 [[NSNotificationCenter defaultCenter] addObserver:self
204 selector:@selector(resetContainers:)
205 name:@"PithosAuthenticationCredentialsUpdated"
212 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
213 PithosNode *node = (PithosNode *)[notification object];
214 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
215 NSInteger lastColumn = [browser lastColumn];
216 for (NSInteger column = lastColumn; column >= 0; column--) {
217 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
218 [browser reloadColumn:column];
224 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
225 BOOL containerPithosFound = NO;
226 BOOL containerTrashFound = NO;
227 NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
228 for (PithosContainerNode *containerNode in accountNode.children) {
229 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
230 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
231 containerPithosFound = YES;
232 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
233 NSUInteger insertIndex = 1;
234 if (!containerPithosFound)
236 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
237 containerTrashFound = YES;
239 [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
242 BOOL refreshAccountNode = NO;
243 if (!containerPithosFound) {
244 // Create pithos node
245 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
246 [containerRequest startSynchronous];
247 if ([containerRequest error]) {
248 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
250 refreshAccountNode = YES;
253 if (!containerTrashFound) {
255 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
256 [containerRequest startSynchronous];
257 if ([containerRequest error]) {
258 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
260 refreshAccountNode = YES;
263 if (refreshAccountNode) {
264 [accountNode refresh];
266 [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
267 self.outlineViewDataSourceArray = outlineViewDataSourceArray;
269 // Expand the folder outline view
270 [outlineView expandItem:nil expandChildren:YES];
271 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
280 - (IBAction)refresh:(id)sender {
281 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
282 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
284 [browser validateVisibleColumns];
288 #pragma NSBrowserDelegate
290 - (id)rootItemForBrowser:(NSBrowser *)browser {
294 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
295 PithosNode *node = (PithosNode *)item;
296 return node.children.count;
299 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
300 PithosNode *node = (PithosNode *)item;
301 return [node.children objectAtIndex:index];
304 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
305 PithosNode *node = (PithosNode *)item;
306 return node.isLeafItem;
309 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
310 PithosNode *node = (PithosNode *)item;
314 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
315 if (sharedPreviewController == nil)
316 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
317 return sharedPreviewController;
320 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
321 // if (!forUserResize) {
322 // id item = [browser parentForItemsInColumn:columnIndex];
323 // if ([self browser:browser isLeafItem:item]) {
324 // suggestedWidth = 200;
327 // return suggestedWidth;
330 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
336 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
337 PithosNode *node = (PithosNode *)item;
338 if (node.shared || ([node class] == [PithosContainerNode class]))
343 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
344 PithosNode *node = (PithosNode *)item;
345 NSString *newName = (NSString *)object;
346 NSUInteger newNameLength = [newName length];
347 NSRange firstSlashRange = [newName rangeOfString:@"/"];
348 if ((newNameLength == 0) ||
349 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
350 ([newName isEqualToString:node.displayName])) {
353 if (([node class] == [PithosObjectNode class]) ||
354 (([node class] == [PithosSubdirNode class]) &&
355 !node.pithosObject.subdir &&
356 [node.pithosObject.name hasSuffix:@"/"])) {
357 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
358 dispatch_async(queue, ^{
359 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
360 if ([newName hasSuffix:@"/"])
361 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
362 NSError *error = nil;
364 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
365 objectName:destinationObjectName
367 isDirectory:&isDirectory]) {
368 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
369 [alert setMessageText:@"Name Taken"];
370 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
371 [alert addButtonWithTitle:@"OK"];
377 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
378 objectName:node.pithosObject.name
379 destinationContainerName:node.pithosContainer.name
380 destinationObjectName:destinationObjectName
383 objectRequest.delegate = self;
384 objectRequest.didFinishSelector = @selector(moveFinished:);
385 objectRequest.didFailSelector = @selector(moveFailed:);
386 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
387 node.parent, @"node",
389 [objectRequest startAsynchronous];
392 } else if ([node class] == [PithosSubdirNode class]) {
393 if (firstSlashRange.length == 1)
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 NSError *error = nil;
400 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
401 objectName:destinationObjectName
403 isDirectory:&isDirectory]) {
404 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
405 [alert setMessageText:@"Name Taken"];
406 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
407 [alert addButtonWithTitle:@"OK"];
413 if (node.pithosObject.subdir)
414 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
415 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
416 objectName:node.pithosObject.name
417 destinationContainerName:node.pithosContainer.name
418 destinationObjectName:destinationObjectName
420 if (objectRequests) {
421 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
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];
435 #pragma mark Drag and Drop source
437 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
438 withEvent:(NSEvent *)event {
439 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
440 __block BOOL result = YES;
441 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
442 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
443 if ([node class] == [PithosContainerNode class]) {
451 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
452 toPasteboard:(NSPasteboard *)pasteboard {
453 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
454 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
455 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
456 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
457 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
458 [propertyList addObject:[node.pithosObject.name pathExtension]];
459 [nodes addObject:node];
462 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
463 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
464 self.draggedNodes = nodes;
465 self.draggedParentNode = [browser parentForItemsInColumn:column];
469 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
470 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
471 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
472 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
473 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
474 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
476 // If the node is a subdir ask if the whole tree should be downloaded
477 if ([node class] == [PithosSubdirNode class]) {
478 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
479 [alert setMessageText:@"Download directory"];
480 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
481 [alert addButtonWithTitle:@"OK"];
482 [alert addButtonWithTitle:@"Cancel"];
483 NSInteger choice = [alert runModal];
484 if (choice == NSAlertFirstButtonReturn) {
485 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
486 dispatch_async(queue, ^{
487 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
488 objectName:node.pithosObject.name
489 toDirectory:[dropDestination path]
491 if (objectRequests) {
492 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
493 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
494 objectRequest.delegate = self;
495 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
496 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
497 [objectRequest startAsynchronous];
503 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
504 objectName:node.pithosObject.name
505 toDirectory:[dropDestination path]
508 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
509 objectRequest.delegate = self;
510 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
511 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
512 [objectRequest startAsynchronous];
519 #pragma mark Drag and Drop destination
521 - (NSDragOperation)browser:aBrowser
522 validateDrop:(id<NSDraggingInfo>)info
523 proposedRow:(NSInteger *)row
524 column:(NSInteger *)column
525 dropOperation:(NSBrowserDropOperation *)dropOperation {
526 NSDragOperation result = NSDragOperationNone;
527 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
528 // For a drop above, the drop is redirected to the parent item
529 if (*dropOperation == NSBrowserDropAbove)
531 // Only allow dropping in folders
534 // Check if the node is not a folder and if so redirect to the parent item
535 if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class])
538 *dropOperation = NSBrowserDropOn;
539 result = NSDragOperationCopy;
541 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
542 // For a drop above, the drop is redirected to the parent item
543 if (*dropOperation == NSBrowserDropAbove)
545 // Only allow dropping in folders
547 PithosNode *dropNode;
549 // Check if the node is not a folder and if so redirect to the parent item
550 dropNode = [browser itemAtRow:*row inColumn:*column];
551 if ([dropNode class] != [PithosSubdirNode class])
555 dropNode = [browser parentForItemsInColumn:*column];
557 if (!dropNode.shared) {
558 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
559 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
560 if ((([dropNode class] == [PithosContainerNode class]) ||
561 dropNode.pithosObject.subdir ||
562 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
563 ![dropNode isEqualTo:draggedParentNode]) {
564 // ![dropNode isEqualTo:draggedParentNode] &&
565 // ![draggedNodes containsObject:dropNode]) {
566 result = NSDragOperationMove;
568 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
569 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
570 if (([dropNode class] == [PithosContainerNode class]) ||
571 dropNode.pithosObject.subdir ||
572 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
573 result = NSDragOperationCopy;
582 - (BOOL)browser:(NSBrowser *)aBrowser
583 acceptDrop:(id<NSDraggingInfo>)info
585 column:(NSInteger)column
586 dropOperation:(NSBrowserDropOperation)dropOperation {
587 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
588 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
589 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
590 if ((column != -1) && (filenames != nil)) {
591 PithosNode *node = nil;
593 node = [browser itemAtRow:row inColumn:column];
595 node = [browser parentForItemsInColumn:column];
596 NSLog(@"drag in node: %@", node.url);
597 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
600 NSFileManager *defaultManager = [NSFileManager defaultManager];
601 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
602 NSString *objectNamePrefix;
603 if ([node class] == [PithosSubdirNode class])
604 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
606 objectNamePrefix = [NSString string];
607 NSUInteger blockSize = node.pithosContainer.blockSize;
608 NSString *blockHash = node.pithosContainer.blockHash;
610 for (NSString *filePath in filenames) {
612 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
615 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
616 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
617 dispatch_async(queue, ^{
618 NSError *error = nil;
619 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
620 if (contentType == nil)
621 contentType = @"application/octet-stream";
623 NSLog(@"contentType detection error: %@", error);
624 NSArray *hashes = nil;
625 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
626 objectName:objectName
627 contentType:contentType
634 objectRequest.delegate = self;
635 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
636 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
637 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
638 containerName, @"containerName",
639 objectName, @"objectName",
640 contentType, @"contentType",
641 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
642 blockHash, @"blockHash",
643 filePath, @"filePath",
646 [NSNumber numberWithUnsignedInteger:10], @"iteration",
648 [objectRequest startAsynchronous];
652 // Upload directory, confirm first
653 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
654 [alert setMessageText:@"Upload directory"];
655 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
656 [alert addButtonWithTitle:@"OK"];
657 [alert addButtonWithTitle:@"Cancel"];
658 NSInteger choice = [alert runModal];
659 if (choice == NSAlertFirstButtonReturn) {
660 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
661 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
662 dispatch_async(queue, ^{
663 NSMutableArray *objectNames = nil;
664 NSMutableArray *contentTypes = nil;
665 NSMutableArray *filePaths = nil;
666 NSMutableArray *hashesArrays = nil;
667 NSMutableArray *directoryObjectRequests = nil;
668 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
669 objectName:objectName
672 forDirectory:filePath
674 objectNames:&objectNames
675 contentTypes:&contentTypes
677 hashesArrays:&hashesArrays
678 directoryObjectRequests:&directoryObjectRequests];
679 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
680 objectRequest.delegate = self;
681 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
682 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
683 [objectRequest startAsynchronous];
685 if (objectRequests) {
686 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
687 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
688 objectRequest.delegate = self;
689 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
690 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
691 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
692 containerName, @"containerName",
693 [objectNames objectAtIndex:i], @"objectName",
694 [contentTypes objectAtIndex:i], @"contentType",
695 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
696 blockHash, @"blockHash",
697 [filePaths objectAtIndex:i], @"filePath",
698 [hashesArrays objectAtIndex:i], @"hashes",
699 [NSNumber numberWithUnsignedInteger:10], @"iteration",
701 [objectRequest startAsynchronous];
712 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
713 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
714 if ((column != -1) && (draggedNodes != nil)) {
715 PithosNode *dropNode = nil;
717 dropNode = [browser itemAtRow:row inColumn:column];
719 dropNode = [browser parentForItemsInColumn:column];
720 NSLog(@"drag local node: %@", dropNode.url);
721 if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
724 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
725 NSString *objectNamePrefix;
726 if ([dropNode class] == [PithosSubdirNode class])
727 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
729 objectNamePrefix = [NSString string];
731 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
732 for (PithosNode *node in draggedNodes) {
733 if (([node class] == [PithosObjectNode class]) ||
734 (([node class] == [PithosSubdirNode class]) &&
735 !node.pithosObject.subdir &&
736 [node.pithosObject.name hasSuffix:@"/"])) {
737 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
738 dispatch_async(queue, ^{
739 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
740 if ([node.pithosObject.name hasSuffix:@"/"])
741 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
742 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
743 objectName:node.pithosObject.name
744 destinationContainerName:containerName
745 destinationObjectName:destinationObjectName
748 objectRequest.delegate = self;
749 objectRequest.didFinishSelector = @selector(moveFinished:);
750 objectRequest.didFailSelector = @selector(moveFailed:);
751 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
752 node.parent, @"node",
753 dropNode, @"dropNode",
755 [objectRequest startAsynchronous];
758 } else if ([node class] == [PithosSubdirNode class]) {
759 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
760 dispatch_async(queue, ^{
761 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
762 if (node.pithosObject.subdir)
763 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
764 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
765 objectName:node.pithosObject.name
766 destinationContainerName:containerName
767 destinationObjectName:destinationObjectName
769 if (objectRequests) {
770 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
771 objectRequest.delegate = self;
772 objectRequest.didFinishSelector = @selector(moveFinished:);
773 objectRequest.didFailSelector = @selector(moveFailed:);
774 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
775 node.parent, @"node",
776 dropNode, @"dropNode",
778 [objectRequest startAsynchronous];
784 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
785 for (PithosNode *node in draggedNodes) {
786 if (([node class] == [PithosObjectNode class]) ||
787 (([node class] == [PithosSubdirNode class]) &&
788 !node.pithosObject.subdir &&
789 [node.pithosObject.name hasSuffix:@"/"])) {
790 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
791 dispatch_async(queue, ^{
792 NSString *destinationObjectName;
793 if (![dropNode isEqualTo:node.parent]) {
794 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
795 if ([node.pithosObject.name hasSuffix:@"/"])
796 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
798 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
799 objectName:node.pithosObject.name];
801 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
802 objectName:node.pithosObject.name
803 destinationContainerName:containerName
804 destinationObjectName:destinationObjectName
807 objectRequest.delegate = self;
808 objectRequest.didFinishSelector = @selector(copyFinished:);
809 objectRequest.didFailSelector = @selector(copyFailed:);
810 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
811 dropNode, @"dropNode",
813 [objectRequest startAsynchronous];
816 } else if ([node class] == [PithosSubdirNode class]) {
817 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
818 dispatch_async(queue, ^{
819 NSString *destinationObjectName;
820 if (![dropNode isEqualTo:node.parent]) {
821 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
822 if (node.pithosObject.subdir)
823 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
825 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
826 subdirName:node.pithosObject.name];
828 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
829 objectName:node.pithosObject.name
830 destinationContainerName:containerName
831 destinationObjectName:destinationObjectName
833 if (objectRequests) {
834 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
835 objectRequest.delegate = self;
836 objectRequest.didFinishSelector = @selector(copyFinished:);
837 objectRequest.didFailSelector = @selector(copyFailed:);
838 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
839 dropNode, @"dropNode",
841 [objectRequest startAsynchronous];
856 #pragma mark ASIHTTPRequestDelegate
858 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
859 NSLog(@"Download completed: %@", [objectRequest url]);
860 if (objectRequest.responseStatusCode == 200) {
861 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
862 NSLog(@"Downloaded 0 bytes");
863 NSFileManager *defaultManager = [NSFileManager defaultManager];
864 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
865 if (![defaultManager fileExistsAtPath:filePath]) {
866 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
867 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
868 [alert setMessageText:@"Create File Error"];
869 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
870 [alert addButtonWithTitle:@"OK"];
876 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
880 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
881 NSLog(@"Download failed");
882 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
885 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
886 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
887 if (objectRequest.responseStatusCode == 201) {
888 NSLog(@"Directory object created: %@", [objectRequest url]);
891 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
895 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
896 NSLog(@"Upload directory object failed");
897 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
900 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
901 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
902 if (objectRequest.responseStatusCode == 201) {
903 NSLog(@"Object created: %@", [objectRequest url]);
904 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
909 } else if (objectRequest.responseStatusCode == 409) {
910 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
911 if (iteration == 0) {
912 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
913 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
914 [alert setMessageText:@"Upload Timeout"];
915 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
916 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
917 [alert addButtonWithTitle:@"OK"];
921 NSLog(@"object is missing hashes: %@", [objectRequest url]);
922 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
923 withMissingHashesResponse:[objectRequest responseString]];
924 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
925 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
926 objectName:@".upload"
927 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
928 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
929 missingBlockIndex:missingBlockIndex];
930 newObjectRequest.delegate = self;
931 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
932 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
933 newObjectRequest.userInfo = objectRequest.userInfo;
934 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
935 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
936 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
937 [newObjectRequest startAsynchronous];
939 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
943 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
944 NSLog(@"Upload using hashmap failed");
945 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
948 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
949 NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
950 if (objectRequest.responseStatusCode == 201) {
951 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
952 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
953 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
954 if (missingBlockIndex == NSNotFound) {
955 NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
956 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
957 objectName:[objectRequest.userInfo objectForKey:@"objectName"]
958 contentType:[objectRequest.userInfo objectForKey:@"contentType"]
959 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
960 blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
961 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
964 newObjectRequest.delegate = self;
965 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
966 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
967 newObjectRequest.userInfo = objectRequest.userInfo;
968 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
969 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
970 [newObjectRequest startAsynchronous];
972 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
973 objectName:@".upload"
974 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
975 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
976 missingBlockIndex:missingBlockIndex];
977 newObjectRequest.delegate = self;
978 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
979 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
980 newObjectRequest.userInfo = objectRequest.userInfo;
981 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
982 [newObjectRequest startAsynchronous];
985 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
989 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
990 NSLog(@"Upload of missing block failed");
991 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
994 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
995 NSLog(@"Move object completed: %@", [objectRequest url]);
996 if (objectRequest.responseStatusCode == 201) {
997 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
998 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1003 if (!node || !dropNode)
1006 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1010 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1011 NSLog(@"Move object failed");
1012 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1015 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1016 NSLog(@"Copy object completed: %@", [objectRequest url]);
1017 if (objectRequest.responseStatusCode == 201) {
1018 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1024 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1028 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1029 NSLog(@"Copy object failed");
1030 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1034 #pragma mark NSSplitViewDelegate
1036 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1037 if (splitView == verticalSplitView)
1040 return ([horizontalSplitView bounds].size.height - 87);
1043 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1044 if (splitView == verticalSplitView)
1047 return ([horizontalSplitView bounds].size.height - 87);
1050 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1051 if (splitView == verticalSplitView) {
1052 if (proposedPosition < 120)
1054 else if (proposedPosition > 220)
1057 return proposedPosition;
1059 return ([horizontalSplitView bounds].size.height - 87);
1064 #pragma mark NSOutlineViewDelegate
1066 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1067 return ([[item representedObject] isLeaf]);
1070 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1071 return (![[item representedObject] isLeaf]);
1074 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1075 PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
1078 [browser loadColumnZero];
1084 #pragma mark NSMenuDelegate
1086 - (void)menuNeedsUpdate:(NSMenu *)menu {
1087 NSInteger column = [browser clickedColumn];
1088 NSInteger row = [browser clickedRow];
1089 [menu removeAllItems];
1090 NSMenuItem *menuItem;
1091 NSString *menuItemTitle;
1092 if ((column == -1) || (row == -1)) {
1093 // General context menu
1094 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1095 PithosNode *menuNode;
1096 if ([menuNodesIndexPaths count] == 0) {
1097 menuNode = [browser parentForItemsInColumn:0];
1098 } else if (([menuNodesIndexPaths count] != 1) ||
1099 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1100 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1102 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1104 if ([menuNode class] == [PithosAccountNode class])
1106 BOOL shared = menuNode.shared;
1110 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1111 [menuItem setRepresentedObject:menuNode];
1112 [menu addItem:menuItem];
1113 [menu addItem:[NSMenuItem separatorItem]];
1117 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1118 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1119 [menu addItem:menuItem];
1123 if (clipboardNodes) {
1124 NSUInteger clipboardNodesCount = [clipboardNodes count];
1125 if (clipboardNodesCount == 0) {
1126 self.clipboardNodes = nil;
1127 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1128 if (clipboardNodesCount == 1)
1129 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1131 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1132 [menu addItem:[NSMenuItem separatorItem]];
1133 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1134 [menuItem setRepresentedObject:menuNode];
1135 [menu addItem:menuItem];
1140 // Node context menu
1141 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1142 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1143 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1144 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1145 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1146 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1149 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1151 NSUInteger menuNodesCount = [menuNodes count];
1152 BOOL shared = ((PithosNode *)[menuNodes objectAtIndex:0]).shared;
1155 // Move to Trash (pithos container only)
1157 if ([rootNode class] == [PithosContainerNode class]) {
1158 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1159 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1160 [menuItem setRepresentedObject:menuNodes];
1161 [menu addItem:menuItem];
1163 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1164 [menuItem setRepresentedObject:menuNodes];
1165 [menu addItem:menuItem];
1166 [menu addItem:[NSMenuItem separatorItem]];
1171 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1172 [menuItem setRepresentedObject:menuNodes];
1173 [menu addItem:menuItem];
1177 [menu addItem:[NSMenuItem separatorItem]];
1178 if (menuNodesCount == 1)
1179 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1181 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1182 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1183 [menuItem setRepresentedObject:menuNodes];
1184 [menu addItem:menuItem];
1186 if (menuNodesCount == 1)
1187 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1189 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1190 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1191 [menuItem setRepresentedObject:menuNodes];
1192 [menu addItem:menuItem];
1194 if (menuNodesCount == 1) {
1195 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1196 if (([menuNode class] == [PithosSubdirNode class]) &&
1197 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1198 if (clipboardNodes) {
1199 NSUInteger clipboardNodesCount = [clipboardNodes count];
1200 if (clipboardNodesCount == 0) {
1201 self.clipboardNodes = nil;
1202 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1203 if (clipboardNodesCount == 1)
1204 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1206 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1207 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1208 [menuItem setRepresentedObject:menuNode];
1209 [menu addItem:menuItem];
1219 #pragma mark Menu Actions
1221 - (void)menuNewFolder:(NSMenuItem *)sender {
1222 PithosNode *node = (PithosNode *)[sender representedObject];
1223 if ([node class] == [PithosContainerNode class]) {
1224 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1225 dispatch_async(queue, ^{
1226 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1227 subdirName:@"untitled folder"];
1228 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1229 objectName:safeObjectName
1231 contentType:@"application/directory"
1233 contentDisposition:nil
1236 isPublic:ASIPithosObjectRequestPublicIgnore
1238 data:[NSData data]];
1239 objectRequest.delegate = self;
1240 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1241 objectRequest.didFailSelector = @selector(newFolderFailed:);
1242 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1245 [objectRequest startAsynchronous];
1247 } else if (([node class] == [PithosSubdirNode class]) &&
1248 (node.pithosObject.subdir ||
1249 ![node.pithosObject.name hasSuffix:@"/"])) {
1250 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1251 dispatch_async(queue, ^{
1252 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1253 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1254 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1255 objectName:safeObjectName
1257 contentType:@"application/directory"
1259 contentDisposition:nil
1262 isPublic:ASIPithosObjectRequestPublicIgnore
1264 data:[NSData data]];
1265 objectRequest.delegate = self;
1266 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1267 objectRequest.didFailSelector = @selector(newFolderFailed:);
1268 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1271 [objectRequest startAsynchronous];
1276 - (void)menuGetInfo:(NSMenuItem *)sender {
1277 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1278 [node showPithosNodeInfo:sender];
1282 - (void)menuDelete:(NSMenuItem *)sender {
1283 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1284 if (([node class] == [PithosObjectNode class]) ||
1285 (([node class] == [PithosSubdirNode class]) &&
1286 !node.pithosObject.subdir &&
1287 [node.pithosObject.name hasSuffix:@"/"])) {
1288 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1289 objectName:node.pithosObject.name];
1290 objectRequest.delegate = self;
1291 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1292 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1293 [objectRequest startAsynchronous];
1294 } else if ([node class] == [PithosSubdirNode class]) {
1295 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1296 dispatch_async(queue, ^{
1297 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1298 objectName:node.pithosObject.name];
1299 if (objectRequests) {
1300 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1301 objectRequest.delegate = self;
1302 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1303 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1304 [objectRequest startAsynchronous];
1312 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1313 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1314 if (([node class] == [PithosObjectNode class]) ||
1315 (([node class] == [PithosSubdirNode class]) &&
1316 !node.pithosObject.subdir &&
1317 [node.pithosObject.name hasSuffix:@"/"])) {
1318 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1319 dispatch_async(queue, ^{
1320 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1321 objectName:node.pithosObject.name];
1322 if (safeObjectName) {
1323 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1324 objectName:node.pithosObject.name
1325 destinationContainerName:@"trash"
1326 destinationObjectName:safeObjectName
1328 if (objectRequest) {
1329 objectRequest.delegate = self;
1330 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1331 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1332 [objectRequest startAsynchronous];
1336 } else if ([node class] == [PithosSubdirNode class]) {
1337 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1338 dispatch_async(queue, ^{
1339 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1340 subdirName:node.pithosObject.name];
1341 if (safeObjectName) {
1342 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1343 objectName:node.pithosObject.name
1344 destinationContainerName:@"trash"
1345 destinationObjectName:safeObjectName
1347 if (objectRequests) {
1348 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1349 objectRequest.delegate = self;
1350 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1351 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1352 [objectRequest startAsynchronous];
1361 - (void)menuCut:(NSMenuItem *)sender {
1362 self.clipboardNodes = (NSArray *)[sender representedObject];
1363 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1364 self.clipboardCopy = NO;
1367 - (void)menuCopy:(NSMenuItem *)sender {
1368 self.clipboardNodes = (NSArray *)[sender representedObject];
1369 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1370 self.clipboardCopy = YES;
1373 - (void)menuPaste:(NSMenuItem *)sender {
1374 if (!clipboardNodes || ![clipboardNodes count])
1376 PithosNode *dropNode = (PithosNode *)[sender representedObject];
1377 if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) ||
1378 (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"]))
1381 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1382 NSString *objectNamePrefix;
1383 if ([dropNode class] == [PithosSubdirNode class])
1384 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1386 objectNamePrefix = [NSString string];
1388 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1389 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1390 self.clipboardNodes = nil;
1391 self.clipboardParentNode = nil;
1392 for (PithosNode *node in localClipboardNodes) {
1393 if (([node class] == [PithosObjectNode class]) ||
1394 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1395 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1396 dispatch_async(queue, ^{
1397 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1398 if ([node.pithosObject.name hasSuffix:@"/"])
1399 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1400 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1401 objectName:node.pithosObject.name
1402 destinationContainerName:containerName
1403 destinationObjectName:destinationObjectName
1405 if (objectRequest) {
1406 objectRequest.delegate = self;
1407 objectRequest.didFinishSelector = @selector(moveFinished:);
1408 objectRequest.didFailSelector = @selector(moveFailed:);
1409 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1410 node.parent, @"node",
1411 dropNode, @"dropNode",
1413 [objectRequest startAsynchronous];
1416 } else if ([node class] == [PithosSubdirNode class]) {
1417 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1418 dispatch_async(queue, ^{
1419 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1420 if (node.pithosObject.subdir)
1421 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1422 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1423 objectName:node.pithosObject.name
1424 destinationContainerName:containerName
1425 destinationObjectName:destinationObjectName
1427 if (objectRequests) {
1428 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1429 objectRequest.delegate = self;
1430 objectRequest.didFinishSelector = @selector(moveFinished:);
1431 objectRequest.didFailSelector = @selector(moveFailed:);
1432 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1433 node.parent, @"node",
1434 dropNode, @"dropNode",
1436 [objectRequest startAsynchronous];
1443 for (PithosNode *node in localClipboardNodes) {
1444 if (([node class] == [PithosObjectNode class]) ||
1445 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1446 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1447 dispatch_async(queue, ^{
1448 NSString *destinationObjectName;
1449 if (![dropNode isEqualTo:node.parent]) {
1450 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1451 if ([node.pithosObject.name hasSuffix:@"/"])
1452 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1454 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
1455 objectName:node.pithosObject.name];
1457 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1458 objectName:node.pithosObject.name
1459 destinationContainerName:containerName
1460 destinationObjectName:destinationObjectName
1462 if (objectRequest) {
1463 objectRequest.delegate = self;
1464 objectRequest.didFinishSelector = @selector(copyFinished:);
1465 objectRequest.didFailSelector = @selector(copyFailed:);
1466 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1467 dropNode, @"dropNode",
1469 [objectRequest startAsynchronous];
1472 } else if ([node class] == [PithosSubdirNode class]) {
1473 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1474 dispatch_async(queue, ^{
1475 NSString *destinationObjectName;
1476 if (![dropNode isEqualTo:node.parent]) {
1477 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1478 if (node.pithosObject.subdir)
1479 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1481 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
1482 subdirName:node.pithosObject.name];
1484 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1485 objectName:node.pithosObject.name
1486 destinationContainerName:containerName
1487 destinationObjectName:destinationObjectName
1489 if (objectRequests) {
1490 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1491 objectRequest.delegate = self;
1492 objectRequest.didFinishSelector = @selector(copyFinished:);
1493 objectRequest.didFailSelector = @selector(copyFailed:);
1494 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1495 dropNode, @"dropNode",
1497 [objectRequest startAsynchronous];
1507 #pragma mark Menu Actions ASIHTTPRequestDelegate
1509 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1510 if (objectRequest.responseStatusCode == 201) {
1511 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1512 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1518 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1522 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1523 NSLog(@"Creation of new application/directory object failed");
1524 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1527 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1528 if (objectRequest.responseStatusCode == 204) {
1529 NSLog(@"Object deleted: %@", [objectRequest url]);
1532 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1536 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1537 NSLog(@"Delete of object failed");
1538 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1541 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1542 if (objectRequest.responseStatusCode == 201) {
1543 NSLog(@"Object moved: %@", [objectRequest url]);
1546 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1550 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1551 NSLog(@"Move of object failed");
1552 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];