2 // PithosBrowserController.m
5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosSharingAccountsNode.h"
45 #import "PithosEmptyNode.h"
46 #import "ImageAndTextCell.h"
47 #import "FileSystemBrowserCell.h"
48 #import "ASIPithosRequest.h"
49 #import "ASIPithosContainerRequest.h"
50 #import "ASIPithosObjectRequest.h"
51 #import "ASIPithosContainer.h"
52 #import "ASIPithosObject.h"
53 #import "PithosFileUtilities.h"
55 @interface PithosBrowserCell : FileSystemBrowserCell {}
58 @implementation PithosBrowserCell
61 if ((self = [super init])) {
62 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
63 [self setEditable:YES];
68 - (void)setObjectValue:(id)object {
69 if ([object isKindOfClass:[PithosNode class]]) {
70 PithosNode *node = (PithosNode *)object;
71 [self setStringValue:node.displayName];
72 [self setImage:node.icon];
74 [super setObjectValue:object];
80 @interface PithosOutlineViewCell : ImageAndTextCell {}
83 @implementation PithosOutlineViewCell
85 - (void)setObjectValue:(id)object {
86 if ([object isKindOfClass:[PithosNode class]]) {
87 PithosNode *node = (PithosNode *)object;
88 [self setStringValue:node.displayName];
89 [self setImage:node.icon];
90 [self setEditable:NO];
92 [super setObjectValue:object];
98 @interface PithosBrowserController (Private) {}
99 - (void)resetContainers:(NSNotification *)notification;
102 @implementation PithosBrowserController
103 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser;
104 @synthesize draggedNodes, draggedParentNode;
105 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
108 #pragma Object Lifecycle
111 return [super initWithWindowNibName:@"PithosBrowserController"];
115 [[NSNotificationCenter defaultCenter] removeObserver:self];
116 [clipboardParentNode release];
117 [clipboardNodes release];
118 [draggedParentNode release];
119 [draggedNodes release];
120 [browserMenu release];
121 [sharedPreviewController release];
122 [othersSharedNode release];
123 [mySharedNode release];
124 [sharedNode release];
125 [containersNodeChildren release];
126 [containersNode release];
127 [accountNode release];
132 - (void)awakeFromNib {
133 [super awakeFromNib];
135 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
136 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
137 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
139 [browser setCellClass:[PithosBrowserCell class]];
141 browserMenu = [[NSMenu alloc] init];
142 [browserMenu setDelegate:self];
143 [browser setMenu:browserMenu];
146 - (void)resetContainers:(NSNotification *)notification {
148 [browser loadColumnZero];
149 [containersNodeChildren removeAllObjects];
150 [outlineView reloadData];
152 // Expand the folder outline view
153 [outlineView expandItem:nil expandChildren:YES];
154 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
157 [accountNode refresh];
160 - (void)windowDidLoad {
161 [super windowDidLoad];
163 accountNode = [[PithosAccountNode alloc] init];
164 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
165 containersNodeChildren = [[NSMutableArray alloc] init];
166 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
167 mySharedNode = [[PithosAccountNode alloc] init];
168 mySharedNode.displayName = @"my shared";
169 mySharedNode.shared = YES;
170 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
171 othersSharedNode = [[PithosSharingAccountsNode alloc] init];
172 othersSharedNode.displayName = @"others shared";
173 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
175 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
177 // Register for updates
178 // PithosContainerNode updates browser nodes
179 [[NSNotificationCenter defaultCenter] addObserver:self
180 selector:@selector(pithosNodeChildrenUpdated:)
181 name:@"PithosContainerNodeChildrenUpdated"
183 // PithosSubdirNode updates browser nodes
184 [[NSNotificationCenter defaultCenter] addObserver:self
185 selector:@selector(pithosNodeChildrenUpdated:)
186 name:@"PithosSubdirNodeChildrenUpdated"
188 // PithosAccountNode accountNode updates outlineView container nodes
189 [[NSNotificationCenter defaultCenter] addObserver:self
190 selector:@selector(pithosAccountNodeChildrenUpdated:)
191 name:@"PithosAccountNodeChildrenUpdated"
193 // PithosAccountNode other than accountNode updates nodes
194 [[NSNotificationCenter defaultCenter] addObserver:self
195 selector:@selector(pithosNodeChildrenUpdated:)
196 name:@"PithosAccountNodeChildrenUpdated"
198 // PithosSharingAccountsNode othersSharedNode updates browser nodes
199 [[NSNotificationCenter defaultCenter] addObserver:self
200 selector:@selector(pithosNodeChildrenUpdated:)
201 name:@"PithosSharingAccountsNodeChildrenUpdated"
202 object:othersSharedNode];
203 // Updated authentication credentials reset containers in the outline view
204 [[NSNotificationCenter defaultCenter] addObserver:self
205 selector:@selector(resetContainers:)
206 name:@"PithosAuthenticationCredentialsUpdated"
208 // Request for browser refresh
209 [[NSNotificationCenter defaultCenter] addObserver:self
210 selector:@selector(pithosBrowserRefreshNeeded:)
211 name:@"PithosBrowserRefreshNeeeded"
216 #pragma mark Observers
218 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
219 PithosNode *node = (PithosNode *)[notification object];
220 if (node == accountNode)
222 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
223 NSInteger lastColumn = [browser lastColumn];
224 for (NSInteger column = lastColumn; column >= 0; column--) {
225 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
226 [browser reloadColumn:column];
232 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
233 BOOL containerPithosFound = NO;
234 BOOL containerTrashFound = NO;
235 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
236 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
237 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
238 [removedContainersNodeChildren addIndex:i];
240 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
241 for (PithosContainerNode *containerNode in accountNode.children) {
242 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
243 if (![containersNodeChildren containsObject:containerNode])
244 [containersNodeChildren insertObject:containerNode atIndex:0];
245 containerPithosFound = YES;
246 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
247 NSUInteger insertIndex = 1;
248 if (!containerPithosFound)
250 if (![containersNodeChildren containsObject:containerNode])
251 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
252 containerTrashFound = YES;
253 } else if (![containersNodeChildren containsObject:containerNode]) {
254 [containersNodeChildren addObject:containerNode];
257 BOOL refreshAccountNode = NO;
258 if (!containerPithosFound) {
259 // Create pithos node
260 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
261 [containerRequest startSynchronous];
262 if ([containerRequest error]) {
263 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
265 refreshAccountNode = YES;
268 if (!containerTrashFound) {
270 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
271 [containerRequest startSynchronous];
272 if ([containerRequest error]) {
273 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
275 refreshAccountNode = YES;
279 if (refreshAccountNode)
280 [accountNode refresh];
282 [outlineView reloadData];
284 // Expand the folder outline view
285 [outlineView expandItem:nil expandChildren:YES];
287 if ((rootNode == containersNode) || (rootNode == sharedNode)) {
288 rootNode = [containersNodeChildren objectAtIndex:0];
289 [browser loadColumnZero];
296 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
303 - (IBAction)refresh:(id)sender {
305 [accountNode refresh];
306 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
307 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
309 [browser validateVisibleColumns];
313 #pragma mark NSBrowserDelegate
315 - (id)rootItemForBrowser:(NSBrowser *)browser {
319 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
320 PithosNode *node = (PithosNode *)item;
321 return node.children.count;
324 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
325 PithosNode *node = (PithosNode *)item;
326 return [node.children objectAtIndex:index];
329 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
330 PithosNode *node = (PithosNode *)item;
331 return node.isLeafItem;
334 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
335 PithosNode *node = (PithosNode *)item;
339 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
340 if (sharedPreviewController == nil)
341 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
342 return sharedPreviewController;
345 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
346 // if (!forUserResize) {
347 // id item = [browser parentForItemsInColumn:columnIndex];
348 // if ([self browser:browser isLeafItem:item]) {
349 // suggestedWidth = 200;
352 // return suggestedWidth;
355 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
361 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
362 PithosNode *node = (PithosNode *)item;
363 if (node.shared || node.sharingAccount ||
364 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
369 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
370 PithosNode *node = (PithosNode *)item;
371 NSString *newName = (NSString *)object;
372 NSUInteger newNameLength = [newName length];
373 NSRange firstSlashRange = [newName rangeOfString:@"/"];
374 if ((newNameLength == 0) ||
375 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
376 ([newName isEqualToString:node.displayName])) {
379 if (([node class] == [PithosObjectNode class]) ||
380 (([node class] == [PithosSubdirNode class]) &&
381 !node.pithosObject.subdir &&
382 [node.pithosObject.name hasSuffix:@"/"])) {
383 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
384 dispatch_async(queue, ^{
385 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
386 if ([newName hasSuffix:@"/"])
387 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
388 NSError *error = nil;
390 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
391 objectName:destinationObjectName
393 isDirectory:&isDirectory]) {
394 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
395 [alert setMessageText:@"Name Taken"];
396 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
397 [alert addButtonWithTitle:@"OK"];
403 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
404 objectName:node.pithosObject.name
405 destinationContainerName:node.pithosContainer.name
406 destinationObjectName:destinationObjectName
409 objectRequest.delegate = self;
410 objectRequest.didFinishSelector = @selector(moveFinished:);
411 objectRequest.didFailSelector = @selector(moveFailed:);
412 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
413 node.parent, @"node",
415 [objectRequest startAsynchronous];
418 } else if ([node class] == [PithosSubdirNode class]) {
419 if (firstSlashRange.length == 1)
421 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
422 dispatch_async(queue, ^{
423 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
424 NSError *error = nil;
426 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
427 objectName:destinationObjectName
429 isDirectory:&isDirectory]) {
430 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
431 [alert setMessageText:@"Name Taken"];
432 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
433 [alert addButtonWithTitle:@"OK"];
439 if (node.pithosObject.subdir)
440 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
441 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
442 objectName:node.pithosObject.name
443 destinationContainerName:node.pithosContainer.name
444 destinationObjectName:destinationObjectName
446 if (objectRequests) {
447 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
448 objectRequest.delegate = self;
449 objectRequest.didFinishSelector = @selector(moveFinished:);
450 objectRequest.didFailSelector = @selector(moveFailed:);
451 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
452 node.parent, @"node",
454 [objectRequest startAsynchronous];
461 #pragma mark Drag and Drop source
463 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
464 withEvent:(NSEvent *)event {
465 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
466 __block BOOL result = YES;
467 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
468 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
469 if ([node class] == [PithosContainerNode class]) {
477 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
478 toPasteboard:(NSPasteboard *)pasteboard {
479 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
480 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
481 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
482 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
483 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
484 [propertyList addObject:[node.pithosObject.name pathExtension]];
485 [nodes addObject:node];
488 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
489 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
490 self.draggedNodes = nodes;
491 self.draggedParentNode = [browser parentForItemsInColumn:column];
495 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
496 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
497 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
498 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
499 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
500 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
502 // If the node is a subdir ask if the whole tree should be downloaded
503 if ([node class] == [PithosSubdirNode class]) {
504 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
505 [alert setMessageText:@"Download directory"];
506 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
507 [alert addButtonWithTitle:@"OK"];
508 [alert addButtonWithTitle:@"Cancel"];
509 NSInteger choice = [alert runModal];
510 if (choice == NSAlertFirstButtonReturn) {
511 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
512 dispatch_async(queue, ^{
513 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
514 objectName:node.pithosObject.name
515 toDirectory:[dropDestination path]
517 sharingAccount:node.sharingAccount];
518 if (objectRequests) {
519 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
520 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
521 objectRequest.delegate = self;
522 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
523 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
524 [objectRequest startAsynchronous];
530 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
531 objectName:node.pithosObject.name
532 toDirectory:[dropDestination path]
534 sharingAccount:node.sharingAccount];
536 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
537 objectRequest.delegate = self;
538 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
539 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
540 [objectRequest startAsynchronous];
547 #pragma mark Drag and Drop destination
549 - (NSDragOperation)browser:aBrowser
550 validateDrop:(id<NSDraggingInfo>)info
551 proposedRow:(NSInteger *)row
552 column:(NSInteger *)column
553 dropOperation:(NSBrowserDropOperation *)dropOperation {
554 NSDragOperation result = NSDragOperationNone;
555 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
556 // For a drop above, the drop is redirected to the parent item
557 if (*dropOperation == NSBrowserDropAbove)
559 // Only allow dropping in folders
561 PithosNode *dropNode;
563 // Check if the node is not a folder and if so redirect to the parent item
564 dropNode = [browser itemAtRow:*row inColumn:*column];
565 if ([dropNode class] != [PithosSubdirNode class])
569 dropNode = [browser parentForItemsInColumn:*column];
571 if (!dropNode.shared && !dropNode.sharingAccount) {
572 *dropOperation = NSBrowserDropOn;
573 result = NSDragOperationCopy;
576 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
577 // For a drop above, the drop is redirected to the parent item
578 if (*dropOperation == NSBrowserDropAbove)
580 // Only allow dropping in folders
582 PithosNode *dropNode;
584 // Check if the node is not a folder and if so redirect to the parent item
585 dropNode = [browser itemAtRow:*row inColumn:*column];
586 if ([dropNode class] != [PithosSubdirNode class])
590 dropNode = [browser parentForItemsInColumn:*column];
592 if (!dropNode.shared && !dropNode.sharingAccount) {
593 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
594 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
595 if ((([dropNode class] == [PithosContainerNode class]) ||
596 dropNode.pithosObject.subdir ||
597 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
598 ![dropNode isEqualTo:draggedParentNode]) {
599 // ![dropNode isEqualTo:draggedParentNode] &&
600 // ![draggedNodes containsObject:dropNode]) {
601 result = NSDragOperationMove;
603 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
604 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
605 if (([dropNode class] == [PithosContainerNode class]) ||
606 dropNode.pithosObject.subdir ||
607 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
608 result = NSDragOperationCopy;
617 - (BOOL)browser:(NSBrowser *)aBrowser
618 acceptDrop:(id<NSDraggingInfo>)info
620 column:(NSInteger)column
621 dropOperation:(NSBrowserDropOperation)dropOperation {
622 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
623 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
624 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
625 if ((column != -1) && (filenames != nil)) {
626 PithosNode *node = nil;
628 node = [browser itemAtRow:row inColumn:column];
630 node = [browser parentForItemsInColumn:column];
631 NSLog(@"drag in node: %@", node.url);
632 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
635 NSFileManager *defaultManager = [NSFileManager defaultManager];
636 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
637 NSString *objectNamePrefix;
638 if ([node class] == [PithosSubdirNode class])
639 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
641 objectNamePrefix = [NSString string];
642 NSUInteger blockSize = node.pithosContainer.blockSize;
643 NSString *blockHash = node.pithosContainer.blockHash;
645 for (NSString *filePath in filenames) {
647 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
650 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
651 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
652 dispatch_async(queue, ^{
653 NSError *error = nil;
654 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
655 if (contentType == nil)
656 contentType = @"application/octet-stream";
658 NSLog(@"contentType detection error: %@", error);
659 NSArray *hashes = nil;
660 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
661 objectName:objectName
662 contentType:contentType
669 objectRequest.delegate = self;
670 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
671 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
672 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
673 containerName, @"containerName",
674 objectName, @"objectName",
675 contentType, @"contentType",
676 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
677 blockHash, @"blockHash",
678 filePath, @"filePath",
681 [NSNumber numberWithUnsignedInteger:10], @"iteration",
683 [objectRequest startAsynchronous];
687 // Upload directory, confirm first
688 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
689 [alert setMessageText:@"Upload directory"];
690 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
691 [alert addButtonWithTitle:@"OK"];
692 [alert addButtonWithTitle:@"Cancel"];
693 NSInteger choice = [alert runModal];
694 if (choice == NSAlertFirstButtonReturn) {
695 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
696 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
697 dispatch_async(queue, ^{
698 NSMutableArray *objectNames = nil;
699 NSMutableArray *contentTypes = nil;
700 NSMutableArray *filePaths = nil;
701 NSMutableArray *hashesArrays = nil;
702 NSMutableArray *directoryObjectRequests = nil;
703 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
704 objectName:objectName
707 forDirectory:filePath
709 objectNames:&objectNames
710 contentTypes:&contentTypes
712 hashesArrays:&hashesArrays
713 directoryObjectRequests:&directoryObjectRequests];
714 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
715 objectRequest.delegate = self;
716 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
717 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
718 [objectRequest startAsynchronous];
720 if (objectRequests) {
721 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
722 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
723 objectRequest.delegate = self;
724 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
725 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
726 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
727 containerName, @"containerName",
728 [objectNames objectAtIndex:i], @"objectName",
729 [contentTypes objectAtIndex:i], @"contentType",
730 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
731 blockHash, @"blockHash",
732 [filePaths objectAtIndex:i], @"filePath",
733 [hashesArrays objectAtIndex:i], @"hashes",
734 [NSNumber numberWithUnsignedInteger:10], @"iteration",
736 [objectRequest startAsynchronous];
747 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
748 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
749 if ((column != -1) && (draggedNodes != nil)) {
750 PithosNode *dropNode = nil;
752 dropNode = [browser itemAtRow:row inColumn:column];
754 dropNode = [browser parentForItemsInColumn:column];
755 NSLog(@"drag local node: %@", dropNode.url);
756 if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
759 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
760 NSString *objectNamePrefix;
761 if ([dropNode class] == [PithosSubdirNode class])
762 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
764 objectNamePrefix = [NSString string];
766 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
767 for (PithosNode *node in draggedNodes) {
768 if (([node class] == [PithosObjectNode class]) ||
769 (([node class] == [PithosSubdirNode class]) &&
770 !node.pithosObject.subdir &&
771 [node.pithosObject.name hasSuffix:@"/"])) {
772 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
773 dispatch_async(queue, ^{
774 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
775 if ([node.pithosObject.name hasSuffix:@"/"])
776 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
777 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
778 objectName:node.pithosObject.name
779 destinationContainerName:containerName
780 destinationObjectName:destinationObjectName
783 objectRequest.delegate = self;
784 objectRequest.didFinishSelector = @selector(moveFinished:);
785 objectRequest.didFailSelector = @selector(moveFailed:);
786 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
787 node.parent, @"node",
788 dropNode, @"dropNode",
790 [objectRequest startAsynchronous];
793 } else if ([node class] == [PithosSubdirNode class]) {
794 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
795 dispatch_async(queue, ^{
796 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
797 if (node.pithosObject.subdir)
798 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
799 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
800 objectName:node.pithosObject.name
801 destinationContainerName:containerName
802 destinationObjectName:destinationObjectName
804 if (objectRequests) {
805 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
806 objectRequest.delegate = self;
807 objectRequest.didFinishSelector = @selector(moveFinished:);
808 objectRequest.didFailSelector = @selector(moveFailed:);
809 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
810 node.parent, @"node",
811 dropNode, @"dropNode",
813 [objectRequest startAsynchronous];
819 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
820 for (PithosNode *node in draggedNodes) {
821 if (([node class] == [PithosObjectNode class]) ||
822 (([node class] == [PithosSubdirNode class]) &&
823 !node.pithosObject.subdir &&
824 [node.pithosObject.name hasSuffix:@"/"])) {
825 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
826 dispatch_async(queue, ^{
827 NSString *destinationObjectName;
828 if (![dropNode isEqualTo:node.parent]) {
829 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
830 if ([node.pithosObject.name hasSuffix:@"/"])
831 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
833 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
834 objectName:node.pithosObject.name];
836 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
837 objectName:node.pithosObject.name
838 destinationContainerName:containerName
839 destinationObjectName:destinationObjectName
843 objectRequest.delegate = self;
844 objectRequest.didFinishSelector = @selector(copyFinished:);
845 objectRequest.didFailSelector = @selector(copyFailed:);
846 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
847 dropNode, @"dropNode",
849 [objectRequest startAsynchronous];
852 } else if ([node class] == [PithosSubdirNode class]) {
853 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
854 dispatch_async(queue, ^{
855 NSString *destinationObjectName;
856 if (![dropNode isEqualTo:node.parent]) {
857 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
858 if (node.pithosObject.subdir)
859 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
861 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
862 subdirName:node.pithosObject.name];
864 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
865 objectName:node.pithosObject.name
866 destinationContainerName:containerName
867 destinationObjectName:destinationObjectName
870 if (objectRequests) {
871 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
872 objectRequest.delegate = self;
873 objectRequest.didFinishSelector = @selector(copyFinished:);
874 objectRequest.didFailSelector = @selector(copyFailed:);
875 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
876 dropNode, @"dropNode",
878 [objectRequest startAsynchronous];
893 #pragma mark ASIHTTPRequestDelegate
895 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
896 NSLog(@"Download completed: %@", [objectRequest url]);
897 if (objectRequest.responseStatusCode == 200) {
898 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
899 NSLog(@"Downloaded 0 bytes");
900 NSFileManager *defaultManager = [NSFileManager defaultManager];
901 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
902 if (![defaultManager fileExistsAtPath:filePath]) {
903 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
904 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
905 [alert setMessageText:@"Create File Error"];
906 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
907 [alert addButtonWithTitle:@"OK"];
913 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
917 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
918 NSLog(@"Download failed");
919 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
922 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
923 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
924 if (objectRequest.responseStatusCode == 201) {
925 NSLog(@"Directory object created: %@", [objectRequest url]);
928 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
932 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
933 NSLog(@"Upload directory object failed");
934 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
937 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
938 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
939 if (objectRequest.responseStatusCode == 201) {
940 NSLog(@"Object created: %@", [objectRequest url]);
941 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
946 } else if (objectRequest.responseStatusCode == 409) {
947 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
948 if (iteration == 0) {
949 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
950 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
951 [alert setMessageText:@"Upload Timeout"];
952 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
953 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
954 [alert addButtonWithTitle:@"OK"];
958 NSLog(@"object is missing hashes: %@", [objectRequest url]);
959 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
960 withMissingHashesResponse:[objectRequest responseString]];
961 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
962 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
963 objectName:@".upload"
964 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
965 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
966 missingBlockIndex:missingBlockIndex];
967 newObjectRequest.delegate = self;
968 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
969 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
970 newObjectRequest.userInfo = objectRequest.userInfo;
971 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
972 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
973 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
974 [newObjectRequest startAsynchronous];
976 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
980 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
981 NSLog(@"Upload using hashmap failed");
982 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
985 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
986 NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
987 if (objectRequest.responseStatusCode == 201) {
988 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
989 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
990 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
991 if (missingBlockIndex == NSNotFound) {
992 NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
993 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
994 objectName:[objectRequest.userInfo objectForKey:@"objectName"]
995 contentType:[objectRequest.userInfo objectForKey:@"contentType"]
996 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
997 blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
998 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1001 newObjectRequest.delegate = self;
1002 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1003 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1004 newObjectRequest.userInfo = objectRequest.userInfo;
1005 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1006 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1007 [newObjectRequest startAsynchronous];
1009 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1010 objectName:@".upload"
1011 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1012 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1013 missingBlockIndex:missingBlockIndex];
1014 newObjectRequest.delegate = self;
1015 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1016 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1017 newObjectRequest.userInfo = objectRequest.userInfo;
1018 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1019 [newObjectRequest startAsynchronous];
1022 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1026 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
1027 NSLog(@"Upload of missing block failed");
1028 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1031 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1032 NSLog(@"Move object completed: %@", [objectRequest url]);
1033 if (objectRequest.responseStatusCode == 201) {
1034 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1035 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1040 if (!node || !dropNode)
1043 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1047 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1048 NSLog(@"Move object failed");
1049 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1052 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1053 NSLog(@"Copy object completed: %@", [objectRequest url]);
1054 if (objectRequest.responseStatusCode == 201) {
1055 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1061 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1065 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1066 NSLog(@"Copy object failed");
1067 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1071 #pragma mark NSSplitViewDelegate
1073 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1074 if (splitView == verticalSplitView)
1077 return ([horizontalSplitView bounds].size.height - 87);
1080 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1081 if (splitView == verticalSplitView)
1084 return ([horizontalSplitView bounds].size.height - 87);
1087 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1088 if (splitView == verticalSplitView) {
1089 if (proposedPosition < 120)
1091 else if (proposedPosition > 220)
1094 return proposedPosition;
1096 return ([horizontalSplitView bounds].size.height - 87);
1101 #pragma mark NSOutlineViewDataSource
1103 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1106 if (item == containersNode)
1107 return containersNodeChildren.count;
1108 if (item == sharedNode)
1113 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1115 return (!index ? containersNode : sharedNode);
1116 if (item == sharedNode)
1117 return (!index ? mySharedNode : othersSharedNode);
1118 return [containersNodeChildren objectAtIndex:index];
1121 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1122 if ((item == containersNode) || (item == sharedNode))
1127 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1128 PithosNode *node = (PithosNode *)item;
1133 #pragma mark NSOutlineViewDelegate
1135 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1136 if ((item == containersNode) || (item == sharedNode))
1141 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1142 if ((item == containersNode) || (item == sharedNode))
1147 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1148 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1151 [browser loadColumnZero];
1157 #pragma mark NSMenuDelegate
1159 - (void)menuNeedsUpdate:(NSMenu *)menu {
1160 NSInteger column = [browser clickedColumn];
1161 NSInteger row = [browser clickedRow];
1162 [menu removeAllItems];
1163 NSMenuItem *menuItem;
1164 NSString *menuItemTitle;
1165 if ((column == -1) || (row == -1)) {
1166 // General context menu
1167 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1168 PithosNode *menuNode;
1169 if ([menuNodesIndexPaths count] == 0) {
1170 menuNode = [browser parentForItemsInColumn:0];
1171 } else if (([menuNodesIndexPaths count] != 1) ||
1172 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1173 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1175 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1177 if (([menuNode class] == [PithosAccountNode class]) || ([menuNode class] == [PithosSharingAccountsNode class]))
1179 BOOL shared = menuNode.shared;
1180 BOOL sharingAccount = (menuNode.sharingAccount != nil);
1182 if (!shared && !sharingAccount) {
1184 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1185 [menuItem setRepresentedObject:menuNode];
1186 [menu addItem:menuItem];
1187 [menu addItem:[NSMenuItem separatorItem]];
1191 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1192 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1193 [menu addItem:menuItem];
1195 if (!shared && !sharingAccount) {
1197 if (clipboardNodes) {
1198 NSUInteger clipboardNodesCount = [clipboardNodes count];
1199 if (clipboardNodesCount == 0) {
1200 self.clipboardNodes = nil;
1201 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1202 if (clipboardNodesCount == 1)
1203 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1205 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1206 [menu addItem:[NSMenuItem separatorItem]];
1207 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1208 [menuItem setRepresentedObject:menuNode];
1209 [menu addItem:menuItem];
1214 // Node context menu
1215 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1216 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1217 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1218 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1219 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1220 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1223 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1225 NSUInteger menuNodesCount = [menuNodes count];
1226 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1227 BOOL shared = firstMenuNode.shared;
1228 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1230 if (!shared && !sharingAccount) {
1231 // Move to Trash (pithos container only)
1233 if ([rootNode class] == [PithosContainerNode class]) {
1234 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1235 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1236 [menuItem setRepresentedObject:menuNodes];
1237 [menu addItem:menuItem];
1239 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1240 [menuItem setRepresentedObject:menuNodes];
1241 [menu addItem:menuItem];
1242 [menu addItem:[NSMenuItem separatorItem]];
1246 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1248 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1249 [menuItem setRepresentedObject:menuNodes];
1250 [menu addItem:menuItem];
1252 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1253 [menu addItem:[NSMenuItem separatorItem]];
1256 if (!shared && !sharingAccount) {
1259 if (menuNodesCount == 1)
1260 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1262 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1263 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1264 [menuItem setRepresentedObject:menuNodes];
1265 [menu addItem:menuItem];
1268 if ((!shared && !sharingAccount) ||
1269 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1271 if (menuNodesCount == 1)
1272 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1274 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1275 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1276 [menuItem setRepresentedObject:menuNodes];
1277 [menu addItem:menuItem];
1280 if (!shared && !sharingAccount) {
1282 if (menuNodesCount == 1) {
1283 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1284 if (([menuNode class] == [PithosSubdirNode class]) &&
1285 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1286 if (clipboardNodes) {
1287 NSUInteger clipboardNodesCount = [clipboardNodes count];
1288 if (clipboardNodesCount == 0) {
1289 self.clipboardNodes = nil;
1290 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1291 if (clipboardNodesCount == 1)
1292 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1294 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1295 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1296 [menuItem setRepresentedObject:menuNode];
1297 [menu addItem:menuItem];
1307 #pragma mark Menu Actions
1309 - (void)menuNewFolder:(NSMenuItem *)sender {
1310 PithosNode *node = (PithosNode *)[sender representedObject];
1311 if ([node class] == [PithosContainerNode class]) {
1312 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1313 dispatch_async(queue, ^{
1314 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1315 subdirName:@"untitled folder"];
1316 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1317 objectName:safeObjectName
1319 contentType:@"application/directory"
1321 contentDisposition:nil
1324 isPublic:ASIPithosObjectRequestPublicIgnore
1326 data:[NSData data]];
1327 objectRequest.delegate = self;
1328 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1329 objectRequest.didFailSelector = @selector(newFolderFailed:);
1330 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1333 [objectRequest startAsynchronous];
1335 } else if (([node class] == [PithosSubdirNode class]) &&
1336 (node.pithosObject.subdir ||
1337 ![node.pithosObject.name hasSuffix:@"/"])) {
1338 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1339 dispatch_async(queue, ^{
1340 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1341 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1342 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1343 objectName:safeObjectName
1345 contentType:@"application/directory"
1347 contentDisposition:nil
1350 isPublic:ASIPithosObjectRequestPublicIgnore
1352 data:[NSData data]];
1353 objectRequest.delegate = self;
1354 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1355 objectRequest.didFailSelector = @selector(newFolderFailed:);
1356 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1359 [objectRequest startAsynchronous];
1364 - (void)menuGetInfo:(NSMenuItem *)sender {
1365 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1366 [node showPithosNodeInfo:sender];
1370 - (void)menuDelete:(NSMenuItem *)sender {
1371 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1372 if (([node class] == [PithosObjectNode class]) ||
1373 (([node class] == [PithosSubdirNode class]) &&
1374 !node.pithosObject.subdir &&
1375 [node.pithosObject.name hasSuffix:@"/"])) {
1376 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1377 objectName:node.pithosObject.name];
1378 objectRequest.delegate = self;
1379 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1380 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1381 [objectRequest startAsynchronous];
1382 } else if ([node class] == [PithosSubdirNode class]) {
1383 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1384 dispatch_async(queue, ^{
1385 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1386 objectName:node.pithosObject.name];
1387 if (objectRequests) {
1388 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1389 objectRequest.delegate = self;
1390 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1391 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1392 [objectRequest startAsynchronous];
1400 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1401 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1402 if (([node class] == [PithosObjectNode class]) ||
1403 (([node class] == [PithosSubdirNode class]) &&
1404 !node.pithosObject.subdir &&
1405 [node.pithosObject.name hasSuffix:@"/"])) {
1406 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1407 dispatch_async(queue, ^{
1408 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1409 objectName:node.pithosObject.name];
1410 if (safeObjectName) {
1411 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1412 objectName:node.pithosObject.name
1413 destinationContainerName:@"trash"
1414 destinationObjectName:safeObjectName
1416 if (objectRequest) {
1417 objectRequest.delegate = self;
1418 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1419 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1420 [objectRequest startAsynchronous];
1424 } else if ([node class] == [PithosSubdirNode class]) {
1425 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1426 dispatch_async(queue, ^{
1427 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1428 subdirName:node.pithosObject.name];
1429 if (safeObjectName) {
1430 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1431 objectName:node.pithosObject.name
1432 destinationContainerName:@"trash"
1433 destinationObjectName:safeObjectName
1435 if (objectRequests) {
1436 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1437 objectRequest.delegate = self;
1438 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1439 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1440 [objectRequest startAsynchronous];
1449 - (void)menuCut:(NSMenuItem *)sender {
1450 self.clipboardNodes = (NSArray *)[sender representedObject];
1451 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1452 self.clipboardCopy = NO;
1455 - (void)menuCopy:(NSMenuItem *)sender {
1456 self.clipboardNodes = (NSArray *)[sender representedObject];
1457 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1458 self.clipboardCopy = YES;
1461 - (void)menuPaste:(NSMenuItem *)sender {
1462 if (!clipboardNodes || ![clipboardNodes count])
1464 PithosNode *dropNode = (PithosNode *)[sender representedObject];
1465 if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) ||
1466 (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"]))
1469 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1470 NSString *objectNamePrefix;
1471 if ([dropNode class] == [PithosSubdirNode class])
1472 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1474 objectNamePrefix = [NSString string];
1476 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1477 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1478 self.clipboardNodes = nil;
1479 self.clipboardParentNode = nil;
1480 for (PithosNode *node in localClipboardNodes) {
1481 if (([node class] == [PithosObjectNode class]) ||
1482 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1483 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1484 dispatch_async(queue, ^{
1485 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1486 if ([node.pithosObject.name hasSuffix:@"/"])
1487 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1488 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1489 objectName:node.pithosObject.name
1490 destinationContainerName:containerName
1491 destinationObjectName:destinationObjectName
1493 if (objectRequest) {
1494 objectRequest.delegate = self;
1495 objectRequest.didFinishSelector = @selector(moveFinished:);
1496 objectRequest.didFailSelector = @selector(moveFailed:);
1497 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1498 node.parent, @"node",
1499 dropNode, @"dropNode",
1501 [objectRequest startAsynchronous];
1504 } else if ([node class] == [PithosSubdirNode class]) {
1505 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1506 dispatch_async(queue, ^{
1507 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1508 if (node.pithosObject.subdir)
1509 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1510 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1511 objectName:node.pithosObject.name
1512 destinationContainerName:containerName
1513 destinationObjectName:destinationObjectName
1515 if (objectRequests) {
1516 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1517 objectRequest.delegate = self;
1518 objectRequest.didFinishSelector = @selector(moveFinished:);
1519 objectRequest.didFailSelector = @selector(moveFailed:);
1520 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1521 node.parent, @"node",
1522 dropNode, @"dropNode",
1524 [objectRequest startAsynchronous];
1531 for (PithosNode *node in localClipboardNodes) {
1532 if (([node class] == [PithosObjectNode class]) ||
1533 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1534 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1535 dispatch_async(queue, ^{
1536 NSString *destinationObjectName;
1537 if (![dropNode isEqualTo:node.parent]) {
1538 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1539 if ([node.pithosObject.name hasSuffix:@"/"])
1540 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1542 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
1543 objectName:node.pithosObject.name];
1545 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1546 objectName:node.pithosObject.name
1547 destinationContainerName:containerName
1548 destinationObjectName:destinationObjectName
1550 sharingAccount:node.sharingAccount];
1551 if (objectRequest) {
1552 objectRequest.delegate = self;
1553 objectRequest.didFinishSelector = @selector(copyFinished:);
1554 objectRequest.didFailSelector = @selector(copyFailed:);
1555 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1556 dropNode, @"dropNode",
1558 [objectRequest startAsynchronous];
1561 } else if ([node class] == [PithosSubdirNode class]) {
1562 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1563 dispatch_async(queue, ^{
1564 NSString *destinationObjectName;
1565 if (![dropNode isEqualTo:node.parent]) {
1566 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1567 if (node.pithosObject.subdir)
1568 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1570 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
1571 subdirName:node.pithosObject.name];
1573 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1574 objectName:node.pithosObject.name
1575 destinationContainerName:containerName
1576 destinationObjectName:destinationObjectName
1578 sharingAccount:node.sharingAccount];
1579 if (objectRequests) {
1580 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1581 objectRequest.delegate = self;
1582 objectRequest.didFinishSelector = @selector(copyFinished:);
1583 objectRequest.didFailSelector = @selector(copyFailed:);
1584 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1585 dropNode, @"dropNode",
1587 [objectRequest startAsynchronous];
1597 #pragma mark Menu Actions ASIHTTPRequestDelegate
1599 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1600 if (objectRequest.responseStatusCode == 201) {
1601 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1602 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1608 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1612 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1613 NSLog(@"Creation of new application/directory object failed");
1614 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1617 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1618 if (objectRequest.responseStatusCode == 204) {
1619 NSLog(@"Object deleted: %@", [objectRequest url]);
1622 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1626 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1627 NSLog(@"Delete of object failed");
1628 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1631 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1632 if (objectRequest.responseStatusCode == 201) {
1633 NSLog(@"Object moved: %@", [objectRequest url]);
1636 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1640 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1641 NSLog(@"Move of object failed");
1642 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];