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 sharingAccount:nil]) {
395 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
396 [alert setMessageText:@"Name Taken"];
397 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
398 [alert addButtonWithTitle:@"OK"];
404 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
405 objectName:node.pithosObject.name
406 destinationContainerName:node.pithosContainer.name
407 destinationObjectName:destinationObjectName
410 objectRequest.delegate = self;
411 objectRequest.didFinishSelector = @selector(moveFinished:);
412 objectRequest.didFailSelector = @selector(moveFailed:);
413 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
414 node.parent, @"node",
416 [objectRequest startAsynchronous];
419 } else if ([node class] == [PithosSubdirNode class]) {
420 if (firstSlashRange.length == 1)
422 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
423 dispatch_async(queue, ^{
424 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
425 NSError *error = nil;
427 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
428 objectName:destinationObjectName
430 isDirectory:&isDirectory
431 sharingAccount:nil]) {
432 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
433 [alert setMessageText:@"Name Taken"];
434 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
435 [alert addButtonWithTitle:@"OK"];
441 if (node.pithosObject.subdir)
442 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
443 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
444 objectName:node.pithosObject.name
445 destinationContainerName:node.pithosContainer.name
446 destinationObjectName:destinationObjectName
448 if (objectRequests) {
449 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
450 objectRequest.delegate = self;
451 objectRequest.didFinishSelector = @selector(moveFinished:);
452 objectRequest.didFailSelector = @selector(moveFailed:);
453 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
454 node.parent, @"node",
456 [objectRequest startAsynchronous];
463 #pragma mark Drag and Drop source
465 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
466 withEvent:(NSEvent *)event {
467 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
468 __block BOOL result = YES;
469 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
470 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
471 if ([node class] == [PithosContainerNode class]) {
479 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
480 toPasteboard:(NSPasteboard *)pasteboard {
481 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
482 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
483 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
484 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
485 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
486 [propertyList addObject:[node.pithosObject.name pathExtension]];
487 [nodes addObject:node];
490 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
491 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
492 self.draggedNodes = nodes;
493 self.draggedParentNode = [browser parentForItemsInColumn:column];
497 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
498 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
499 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
500 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
501 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
502 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
504 // If the node is a subdir ask if the whole tree should be downloaded
505 if ([node class] == [PithosSubdirNode class]) {
506 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
507 [alert setMessageText:@"Download directory"];
508 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
509 [alert addButtonWithTitle:@"OK"];
510 [alert addButtonWithTitle:@"Cancel"];
511 NSInteger choice = [alert runModal];
512 if (choice == NSAlertFirstButtonReturn) {
513 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
514 dispatch_async(queue, ^{
515 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
516 objectName:node.pithosObject.name
517 toDirectory:[dropDestination path]
519 sharingAccount:node.sharingAccount];
520 if (objectRequests) {
521 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
522 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
523 objectRequest.delegate = self;
524 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
525 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
526 [objectRequest startAsynchronous];
532 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
533 objectName:node.pithosObject.name
534 toDirectory:[dropDestination path]
536 sharingAccount:node.sharingAccount];
538 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
539 objectRequest.delegate = self;
540 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
541 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
542 [objectRequest startAsynchronous];
549 #pragma mark Drag and Drop destination
551 - (NSDragOperation)browser:aBrowser
552 validateDrop:(id<NSDraggingInfo>)info
553 proposedRow:(NSInteger *)row
554 column:(NSInteger *)column
555 dropOperation:(NSBrowserDropOperation *)dropOperation {
556 NSDragOperation result = NSDragOperationNone;
557 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
558 // For a drop above, the drop is redirected to the parent item
559 if (*dropOperation == NSBrowserDropAbove)
561 // Only allow dropping in folders
563 PithosNode *dropNode;
565 // Check if the node is not a folder and if so redirect to the parent item
566 dropNode = [browser itemAtRow:*row inColumn:*column];
567 if ([dropNode class] == [PithosObjectNode class])
571 dropNode = [browser parentForItemsInColumn:*column];
573 if (!dropNode.shared &&
574 (!dropNode.sharingAccount ||
575 ([dropNode class] == [PithosSubdirNode class]) ||
576 ([dropNode class] == [PithosContainerNode class]))) {
577 *dropOperation = NSBrowserDropOn;
578 result = NSDragOperationCopy;
581 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
582 // For a drop above, the drop is redirected to the parent item
583 if (*dropOperation == NSBrowserDropAbove)
585 // Only allow dropping in folders
587 PithosNode *dropNode;
589 // Check if the node is not a folder and if so redirect to the parent item
590 dropNode = [browser itemAtRow:*row inColumn:*column];
591 if ([dropNode class] == [PithosObjectNode class])
595 dropNode = [browser parentForItemsInColumn:*column];
597 if (!dropNode.shared && !dropNode.sharingAccount) {
598 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
599 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
600 if ((([dropNode class] == [PithosContainerNode class]) ||
601 dropNode.pithosObject.subdir ||
602 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
603 ![dropNode isEqualTo:draggedParentNode]) {
604 // ![dropNode isEqualTo:draggedParentNode] &&
605 // ![draggedNodes containsObject:dropNode]) {
606 result = NSDragOperationMove;
608 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
609 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
610 if (([dropNode class] == [PithosContainerNode class]) ||
611 dropNode.pithosObject.subdir ||
612 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
613 result = NSDragOperationCopy;
622 - (BOOL)browser:(NSBrowser *)aBrowser
623 acceptDrop:(id<NSDraggingInfo>)info
625 column:(NSInteger)column
626 dropOperation:(NSBrowserDropOperation)dropOperation {
627 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
628 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
629 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
630 if ((column != -1) && (filenames != nil)) {
631 PithosNode *node = nil;
633 node = [browser itemAtRow:row inColumn:column];
635 node = [browser parentForItemsInColumn:column];
636 NSLog(@"drag in node: %@", node.url);
637 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
640 NSFileManager *defaultManager = [NSFileManager defaultManager];
641 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
642 NSString *objectNamePrefix;
643 if ([node class] == [PithosSubdirNode class])
644 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
646 objectNamePrefix = [NSString string];
647 NSUInteger blockSize = node.pithosContainer.blockSize;
648 NSString *blockHash = node.pithosContainer.blockHash;
650 for (NSString *filePath in filenames) {
652 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
655 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
656 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
657 dispatch_async(queue, ^{
658 NSError *error = nil;
659 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
660 if (contentType == nil)
661 contentType = @"application/octet-stream";
663 NSLog(@"contentType detection error: %@", error);
664 NSArray *hashes = nil;
665 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
666 objectName:objectName
667 contentType:contentType
673 sharingAccount:node.sharingAccount];
675 objectRequest.delegate = self;
676 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
677 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
678 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
679 containerName, @"containerName",
680 objectName, @"objectName",
681 contentType, @"contentType",
682 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
683 blockHash, @"blockHash",
684 filePath, @"filePath",
687 [NSNumber numberWithUnsignedInteger:10], @"iteration",
689 if (node.sharingAccount)
690 [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
691 objectRequest.userInfo = userInfo;
692 [objectRequest startAsynchronous];
696 // Upload directory, confirm first
697 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
698 [alert setMessageText:@"Upload directory"];
699 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
700 [alert addButtonWithTitle:@"OK"];
701 [alert addButtonWithTitle:@"Cancel"];
702 NSInteger choice = [alert runModal];
703 if (choice == NSAlertFirstButtonReturn) {
704 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
705 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
706 dispatch_async(queue, ^{
707 NSMutableArray *objectNames = nil;
708 NSMutableArray *contentTypes = nil;
709 NSMutableArray *filePaths = nil;
710 NSMutableArray *hashesArrays = nil;
711 NSMutableArray *directoryObjectRequests = nil;
712 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
713 objectName:objectName
716 forDirectory:filePath
718 objectNames:&objectNames
719 contentTypes:&contentTypes
721 hashesArrays:&hashesArrays
722 directoryObjectRequests:&directoryObjectRequests
723 sharingAccount:node.sharingAccount];
724 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
725 objectRequest.delegate = self;
726 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
727 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
728 [objectRequest startAsynchronous];
730 if (objectRequests) {
731 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
732 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
733 objectRequest.delegate = self;
734 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
735 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
736 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
737 containerName, @"containerName",
738 [objectNames objectAtIndex:i], @"objectName",
739 [contentTypes objectAtIndex:i], @"contentType",
740 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
741 blockHash, @"blockHash",
742 [filePaths objectAtIndex:i], @"filePath",
743 [hashesArrays objectAtIndex:i], @"hashes",
744 [NSNumber numberWithUnsignedInteger:10], @"iteration",
746 if (node.sharingAccount)
747 [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
748 objectRequest.userInfo = userInfo;
749 [objectRequest startAsynchronous];
760 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
761 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
762 if ((column != -1) && (draggedNodes != nil)) {
763 PithosNode *dropNode = nil;
765 dropNode = [browser itemAtRow:row inColumn:column];
767 dropNode = [browser parentForItemsInColumn:column];
768 NSLog(@"drag local node: %@", dropNode.url);
769 if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
772 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
773 NSString *objectNamePrefix;
774 if ([dropNode class] == [PithosSubdirNode class])
775 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
777 objectNamePrefix = [NSString string];
779 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
780 for (PithosNode *node in draggedNodes) {
781 if (([node class] == [PithosObjectNode class]) ||
782 (([node class] == [PithosSubdirNode class]) &&
783 !node.pithosObject.subdir &&
784 [node.pithosObject.name hasSuffix:@"/"])) {
785 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
786 dispatch_async(queue, ^{
787 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
788 if ([node.pithosObject.name hasSuffix:@"/"])
789 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
790 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
791 objectName:node.pithosObject.name
792 destinationContainerName:containerName
793 destinationObjectName:destinationObjectName
796 objectRequest.delegate = self;
797 objectRequest.didFinishSelector = @selector(moveFinished:);
798 objectRequest.didFailSelector = @selector(moveFailed:);
799 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
800 node.parent, @"node",
801 dropNode, @"dropNode",
803 [objectRequest startAsynchronous];
806 } else if ([node class] == [PithosSubdirNode class]) {
807 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
808 dispatch_async(queue, ^{
809 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
810 if (node.pithosObject.subdir)
811 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
812 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
813 objectName:node.pithosObject.name
814 destinationContainerName:containerName
815 destinationObjectName:destinationObjectName
817 if (objectRequests) {
818 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
819 objectRequest.delegate = self;
820 objectRequest.didFinishSelector = @selector(moveFinished:);
821 objectRequest.didFailSelector = @selector(moveFailed:);
822 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
823 node.parent, @"node",
824 dropNode, @"dropNode",
826 [objectRequest startAsynchronous];
832 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
833 for (PithosNode *node in draggedNodes) {
834 if (([node class] == [PithosObjectNode class]) ||
835 (([node class] == [PithosSubdirNode class]) &&
836 !node.pithosObject.subdir &&
837 [node.pithosObject.name hasSuffix:@"/"])) {
838 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
839 dispatch_async(queue, ^{
840 NSString *destinationObjectName;
841 if (![dropNode isEqualTo:node.parent]) {
842 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
843 if ([node.pithosObject.name hasSuffix:@"/"])
844 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
846 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
847 objectName:node.pithosObject.name];
849 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
850 objectName:node.pithosObject.name
851 destinationContainerName:containerName
852 destinationObjectName:destinationObjectName
856 objectRequest.delegate = self;
857 objectRequest.didFinishSelector = @selector(copyFinished:);
858 objectRequest.didFailSelector = @selector(copyFailed:);
859 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
860 dropNode, @"dropNode",
862 [objectRequest startAsynchronous];
865 } else if ([node class] == [PithosSubdirNode class]) {
866 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
867 dispatch_async(queue, ^{
868 NSString *destinationObjectName;
869 if (![dropNode isEqualTo:node.parent]) {
870 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
871 if (node.pithosObject.subdir)
872 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
874 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
875 subdirName:node.pithosObject.name];
877 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
878 objectName:node.pithosObject.name
879 destinationContainerName:containerName
880 destinationObjectName:destinationObjectName
883 if (objectRequests) {
884 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
885 objectRequest.delegate = self;
886 objectRequest.didFinishSelector = @selector(copyFinished:);
887 objectRequest.didFailSelector = @selector(copyFailed:);
888 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
889 dropNode, @"dropNode",
891 [objectRequest startAsynchronous];
906 #pragma mark ASIHTTPRequestDelegate
908 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
909 NSLog(@"Download completed: %@", [objectRequest url]);
910 if (objectRequest.responseStatusCode == 200) {
911 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
912 NSLog(@"Downloaded 0 bytes");
913 NSFileManager *defaultManager = [NSFileManager defaultManager];
914 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
915 if (![defaultManager fileExistsAtPath:filePath]) {
916 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
917 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
918 [alert setMessageText:@"Create File Error"];
919 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
920 [alert addButtonWithTitle:@"OK"];
926 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
930 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
931 NSLog(@"Download failed");
932 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
935 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
936 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
937 if (objectRequest.responseStatusCode == 201) {
938 NSLog(@"Directory object created: %@", [objectRequest url]);
941 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
945 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
946 NSLog(@"Upload directory object failed");
947 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
950 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
951 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
952 if (objectRequest.responseStatusCode == 201) {
953 NSLog(@"Object created: %@", [objectRequest url]);
954 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
959 } else if (objectRequest.responseStatusCode == 409) {
960 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
961 if (iteration == 0) {
962 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
963 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
964 [alert setMessageText:@"Upload Timeout"];
965 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
966 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
967 [alert addButtonWithTitle:@"OK"];
971 NSLog(@"object is missing hashes: %@", [objectRequest url]);
972 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
973 withMissingHashesResponse:[objectRequest responseString]];
974 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
975 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
976 objectName:@".upload"
977 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
978 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
979 missingBlockIndex:missingBlockIndex
980 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
981 newObjectRequest.delegate = self;
982 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
983 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
984 newObjectRequest.userInfo = objectRequest.userInfo;
985 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
986 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
987 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
988 [newObjectRequest startAsynchronous];
990 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
994 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
995 NSLog(@"Upload using hashmap failed");
996 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
999 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
1000 NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
1001 if (objectRequest.responseStatusCode == 201) {
1002 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
1003 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1004 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1005 if (missingBlockIndex == NSNotFound) {
1006 NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
1007 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1008 objectName:[objectRequest.userInfo objectForKey:@"objectName"]
1009 contentType:[objectRequest.userInfo objectForKey:@"contentType"]
1010 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1011 blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
1012 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1015 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1016 newObjectRequest.delegate = self;
1017 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1018 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1019 newObjectRequest.userInfo = objectRequest.userInfo;
1020 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1021 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1022 [newObjectRequest startAsynchronous];
1024 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1025 objectName:@".upload"
1026 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1027 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1028 missingBlockIndex:missingBlockIndex
1029 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1030 newObjectRequest.delegate = self;
1031 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1032 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1033 newObjectRequest.userInfo = objectRequest.userInfo;
1034 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1035 [newObjectRequest startAsynchronous];
1038 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1042 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
1043 NSLog(@"Upload of missing block failed");
1044 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1047 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1048 NSLog(@"Move object completed: %@", [objectRequest url]);
1049 if (objectRequest.responseStatusCode == 201) {
1050 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1051 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1056 if (!node || !dropNode)
1059 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1063 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1064 NSLog(@"Move object failed");
1065 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1068 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1069 NSLog(@"Copy object completed: %@", [objectRequest url]);
1070 if (objectRequest.responseStatusCode == 201) {
1071 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1077 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1081 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1082 NSLog(@"Copy object failed");
1083 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1087 #pragma mark NSSplitViewDelegate
1089 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1090 if (splitView == verticalSplitView)
1093 return ([horizontalSplitView bounds].size.height - 87);
1096 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1097 if (splitView == verticalSplitView)
1100 return ([horizontalSplitView bounds].size.height - 87);
1103 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1104 if (splitView == verticalSplitView) {
1105 if (proposedPosition < 120)
1107 else if (proposedPosition > 220)
1110 return proposedPosition;
1112 return ([horizontalSplitView bounds].size.height - 87);
1117 #pragma mark NSOutlineViewDataSource
1119 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1122 if (item == containersNode)
1123 return containersNodeChildren.count;
1124 if (item == sharedNode)
1129 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1131 return (!index ? containersNode : sharedNode);
1132 if (item == sharedNode)
1133 return (!index ? mySharedNode : othersSharedNode);
1134 return [containersNodeChildren objectAtIndex:index];
1137 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1138 if ((item == containersNode) || (item == sharedNode))
1143 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1144 PithosNode *node = (PithosNode *)item;
1149 #pragma mark NSOutlineViewDelegate
1151 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1152 if ((item == containersNode) || (item == sharedNode))
1157 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1158 if ((item == containersNode) || (item == sharedNode))
1163 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1164 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1167 [browser loadColumnZero];
1173 #pragma mark NSMenuDelegate
1175 - (void)menuNeedsUpdate:(NSMenu *)menu {
1176 NSInteger column = [browser clickedColumn];
1177 NSInteger row = [browser clickedRow];
1178 [menu removeAllItems];
1179 NSMenuItem *menuItem;
1180 NSString *menuItemTitle;
1181 if ((column == -1) || (row == -1)) {
1182 // General context menu
1183 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1184 PithosNode *menuNode;
1185 if ([menuNodesIndexPaths count] == 0) {
1186 menuNode = [browser parentForItemsInColumn:0];
1187 } else if (([menuNodesIndexPaths count] != 1) ||
1188 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1189 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1191 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1193 if (([menuNode class] == [PithosAccountNode class]) || ([menuNode class] == [PithosSharingAccountsNode class]))
1195 BOOL shared = menuNode.shared;
1196 BOOL sharingAccount = (menuNode.sharingAccount != nil);
1198 if (!shared && !sharingAccount) {
1200 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1201 [menuItem setRepresentedObject:menuNode];
1202 [menu addItem:menuItem];
1203 [menu addItem:[NSMenuItem separatorItem]];
1207 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1208 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1209 [menu addItem:menuItem];
1211 if (!shared && !sharingAccount) {
1213 if (clipboardNodes) {
1214 NSUInteger clipboardNodesCount = [clipboardNodes count];
1215 if (clipboardNodesCount == 0) {
1216 self.clipboardNodes = nil;
1217 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1218 if (clipboardNodesCount == 1)
1219 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1221 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1222 [menu addItem:[NSMenuItem separatorItem]];
1223 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1224 [menuItem setRepresentedObject:menuNode];
1225 [menu addItem:menuItem];
1230 // Node context menu
1231 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1232 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1233 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1234 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1235 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1236 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1239 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1241 NSUInteger menuNodesCount = [menuNodes count];
1242 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1243 BOOL shared = firstMenuNode.shared;
1244 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1246 if (!shared && !sharingAccount) {
1247 // Move to Trash (pithos container only)
1249 if ([rootNode class] == [PithosContainerNode class]) {
1250 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1251 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1252 [menuItem setRepresentedObject:menuNodes];
1253 [menu addItem:menuItem];
1255 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1256 [menuItem setRepresentedObject:menuNodes];
1257 [menu addItem:menuItem];
1258 [menu addItem:[NSMenuItem separatorItem]];
1262 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1264 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1265 [menuItem setRepresentedObject:menuNodes];
1266 [menu addItem:menuItem];
1268 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1269 [menu addItem:[NSMenuItem separatorItem]];
1272 if (!shared && !sharingAccount) {
1275 if (menuNodesCount == 1)
1276 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1278 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1279 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1280 [menuItem setRepresentedObject:menuNodes];
1281 [menu addItem:menuItem];
1284 if ((!shared && !sharingAccount) ||
1285 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1287 if (menuNodesCount == 1)
1288 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1290 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1291 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1292 [menuItem setRepresentedObject:menuNodes];
1293 [menu addItem:menuItem];
1296 if (!shared && !sharingAccount) {
1298 if (menuNodesCount == 1) {
1299 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1300 if (([menuNode class] == [PithosSubdirNode class]) &&
1301 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1302 if (clipboardNodes) {
1303 NSUInteger clipboardNodesCount = [clipboardNodes count];
1304 if (clipboardNodesCount == 0) {
1305 self.clipboardNodes = nil;
1306 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1307 if (clipboardNodesCount == 1)
1308 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1310 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1311 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1312 [menuItem setRepresentedObject:menuNode];
1313 [menu addItem:menuItem];
1323 #pragma mark Menu Actions
1325 - (void)menuNewFolder:(NSMenuItem *)sender {
1326 PithosNode *node = (PithosNode *)[sender representedObject];
1327 if ([node class] == [PithosContainerNode class]) {
1328 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1329 dispatch_async(queue, ^{
1330 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1331 subdirName:@"untitled folder"];
1332 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1333 objectName:safeObjectName
1335 contentType:@"application/directory"
1337 contentDisposition:nil
1340 isPublic:ASIPithosObjectRequestPublicIgnore
1342 data:[NSData data]];
1343 objectRequest.delegate = self;
1344 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1345 objectRequest.didFailSelector = @selector(newFolderFailed:);
1346 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1349 [objectRequest startAsynchronous];
1351 } else if (([node class] == [PithosSubdirNode class]) &&
1352 (node.pithosObject.subdir ||
1353 ![node.pithosObject.name hasSuffix:@"/"])) {
1354 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1355 dispatch_async(queue, ^{
1356 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1357 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1358 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1359 objectName:safeObjectName
1361 contentType:@"application/directory"
1363 contentDisposition:nil
1366 isPublic:ASIPithosObjectRequestPublicIgnore
1368 data:[NSData data]];
1369 objectRequest.delegate = self;
1370 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1371 objectRequest.didFailSelector = @selector(newFolderFailed:);
1372 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1375 [objectRequest startAsynchronous];
1380 - (void)menuGetInfo:(NSMenuItem *)sender {
1381 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1382 [node showPithosNodeInfo:sender];
1386 - (void)menuDelete:(NSMenuItem *)sender {
1387 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1388 if (([node class] == [PithosObjectNode class]) ||
1389 (([node class] == [PithosSubdirNode class]) &&
1390 !node.pithosObject.subdir &&
1391 [node.pithosObject.name hasSuffix:@"/"])) {
1392 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1393 objectName:node.pithosObject.name];
1394 objectRequest.delegate = self;
1395 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1396 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1397 [objectRequest startAsynchronous];
1398 } else if ([node class] == [PithosSubdirNode class]) {
1399 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1400 dispatch_async(queue, ^{
1401 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1402 objectName:node.pithosObject.name];
1403 if (objectRequests) {
1404 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1405 objectRequest.delegate = self;
1406 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1407 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1408 [objectRequest startAsynchronous];
1416 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1417 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1418 if (([node class] == [PithosObjectNode class]) ||
1419 (([node class] == [PithosSubdirNode class]) &&
1420 !node.pithosObject.subdir &&
1421 [node.pithosObject.name hasSuffix:@"/"])) {
1422 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1423 dispatch_async(queue, ^{
1424 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1425 objectName:node.pithosObject.name];
1426 if (safeObjectName) {
1427 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1428 objectName:node.pithosObject.name
1429 destinationContainerName:@"trash"
1430 destinationObjectName:safeObjectName
1432 if (objectRequest) {
1433 objectRequest.delegate = self;
1434 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1435 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1436 [objectRequest startAsynchronous];
1440 } else if ([node class] == [PithosSubdirNode class]) {
1441 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1442 dispatch_async(queue, ^{
1443 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1444 subdirName:node.pithosObject.name];
1445 if (safeObjectName) {
1446 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1447 objectName:node.pithosObject.name
1448 destinationContainerName:@"trash"
1449 destinationObjectName:safeObjectName
1451 if (objectRequests) {
1452 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1453 objectRequest.delegate = self;
1454 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1455 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1456 [objectRequest startAsynchronous];
1465 - (void)menuCut:(NSMenuItem *)sender {
1466 self.clipboardNodes = (NSArray *)[sender representedObject];
1467 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1468 self.clipboardCopy = NO;
1471 - (void)menuCopy:(NSMenuItem *)sender {
1472 self.clipboardNodes = (NSArray *)[sender representedObject];
1473 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1474 self.clipboardCopy = YES;
1477 - (void)menuPaste:(NSMenuItem *)sender {
1478 if (!clipboardNodes || ![clipboardNodes count])
1480 PithosNode *dropNode = (PithosNode *)[sender representedObject];
1481 if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) ||
1482 (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"]))
1485 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1486 NSString *objectNamePrefix;
1487 if ([dropNode class] == [PithosSubdirNode class])
1488 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1490 objectNamePrefix = [NSString string];
1492 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1493 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1494 self.clipboardNodes = nil;
1495 self.clipboardParentNode = nil;
1496 for (PithosNode *node in localClipboardNodes) {
1497 if (([node class] == [PithosObjectNode class]) ||
1498 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1499 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1500 dispatch_async(queue, ^{
1501 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1502 if ([node.pithosObject.name hasSuffix:@"/"])
1503 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1504 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1505 objectName:node.pithosObject.name
1506 destinationContainerName:containerName
1507 destinationObjectName:destinationObjectName
1509 if (objectRequest) {
1510 objectRequest.delegate = self;
1511 objectRequest.didFinishSelector = @selector(moveFinished:);
1512 objectRequest.didFailSelector = @selector(moveFailed:);
1513 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1514 node.parent, @"node",
1515 dropNode, @"dropNode",
1517 [objectRequest startAsynchronous];
1520 } else if ([node class] == [PithosSubdirNode class]) {
1521 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1522 dispatch_async(queue, ^{
1523 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1524 if (node.pithosObject.subdir)
1525 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1526 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1527 objectName:node.pithosObject.name
1528 destinationContainerName:containerName
1529 destinationObjectName:destinationObjectName
1531 if (objectRequests) {
1532 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1533 objectRequest.delegate = self;
1534 objectRequest.didFinishSelector = @selector(moveFinished:);
1535 objectRequest.didFailSelector = @selector(moveFailed:);
1536 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1537 node.parent, @"node",
1538 dropNode, @"dropNode",
1540 [objectRequest startAsynchronous];
1547 for (PithosNode *node in localClipboardNodes) {
1548 if (([node class] == [PithosObjectNode class]) ||
1549 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1550 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1551 dispatch_async(queue, ^{
1552 NSString *destinationObjectName;
1553 if (![dropNode isEqualTo:node.parent]) {
1554 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1555 if ([node.pithosObject.name hasSuffix:@"/"])
1556 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1558 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
1559 objectName:node.pithosObject.name];
1561 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1562 objectName:node.pithosObject.name
1563 destinationContainerName:containerName
1564 destinationObjectName:destinationObjectName
1566 sharingAccount:node.sharingAccount];
1567 if (objectRequest) {
1568 objectRequest.delegate = self;
1569 objectRequest.didFinishSelector = @selector(copyFinished:);
1570 objectRequest.didFailSelector = @selector(copyFailed:);
1571 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1572 dropNode, @"dropNode",
1574 [objectRequest startAsynchronous];
1577 } else if ([node class] == [PithosSubdirNode class]) {
1578 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1579 dispatch_async(queue, ^{
1580 NSString *destinationObjectName;
1581 if (![dropNode isEqualTo:node.parent]) {
1582 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1583 if (node.pithosObject.subdir)
1584 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1586 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
1587 subdirName:node.pithosObject.name];
1589 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1590 objectName:node.pithosObject.name
1591 destinationContainerName:containerName
1592 destinationObjectName:destinationObjectName
1594 sharingAccount:node.sharingAccount];
1595 if (objectRequests) {
1596 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1597 objectRequest.delegate = self;
1598 objectRequest.didFinishSelector = @selector(copyFinished:);
1599 objectRequest.didFailSelector = @selector(copyFailed:);
1600 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1601 dropNode, @"dropNode",
1603 [objectRequest startAsynchronous];
1613 #pragma mark Menu Actions ASIHTTPRequestDelegate
1615 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1616 if (objectRequest.responseStatusCode == 201) {
1617 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1618 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1624 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1628 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1629 NSLog(@"Creation of new application/directory object failed");
1630 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1633 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1634 if (objectRequest.responseStatusCode == 204) {
1635 NSLog(@"Object deleted: %@", [objectRequest url]);
1638 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1642 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1643 NSLog(@"Delete of object failed");
1644 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1647 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1648 if (objectRequest.responseStatusCode == 201) {
1649 NSLog(@"Object moved: %@", [objectRequest url]);
1652 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1656 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1657 NSLog(@"Move of object failed");
1658 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];