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;
100 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
101 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
102 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
105 @implementation PithosBrowserController
106 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser;
107 @synthesize draggedNodes, draggedParentNode;
108 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
111 #pragma Object Lifecycle
114 return [super initWithWindowNibName:@"PithosBrowserController"];
118 [[NSNotificationCenter defaultCenter] removeObserver:self];
119 [clipboardParentNode release];
120 [clipboardNodes release];
121 [draggedParentNode release];
122 [draggedNodes release];
123 [outlineViewMenu release];
124 [browserMenu release];
125 [sharedPreviewController release];
126 [othersSharedNode release];
127 [mySharedNode release];
128 [sharedNode release];
129 [containersNodeChildren release];
130 [containersNode release];
131 [accountNode release];
136 - (void)awakeFromNib {
137 [super awakeFromNib];
139 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
140 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
141 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
143 [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
144 [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
145 [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
147 [browser setCellClass:[PithosBrowserCell class]];
149 browserMenu = [[NSMenu alloc] init];
150 [browserMenu setDelegate:self];
151 [browser setMenu:browserMenu];
153 outlineViewMenu = [[NSMenu alloc] init];
154 [outlineViewMenu setDelegate:self];
155 [outlineView setMenu:outlineViewMenu];
158 - (void)resetContainers:(NSNotification *)notification {
160 [browser loadColumnZero];
161 [containersNodeChildren removeAllObjects];
162 [outlineView reloadData];
163 // Expand the folder outline view
164 [outlineView expandItem:nil expandChildren:YES];
165 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
168 [accountNode refresh];
169 [mySharedNode refresh];
170 [othersSharedNode refresh];
173 - (void)windowDidLoad {
174 [super windowDidLoad];
176 accountNode = [[PithosAccountNode alloc] init];
177 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
178 containersNodeChildren = [[NSMutableArray alloc] init];
179 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
180 mySharedNode = [[PithosAccountNode alloc] init];
181 mySharedNode.displayName = @"my shared";
182 mySharedNode.shared = YES;
183 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
184 othersSharedNode = [[PithosSharingAccountsNode alloc] init];
185 othersSharedNode.displayName = @"others shared";
186 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
188 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
190 // Register for updates
191 // PithosContainerNode updates browser nodes
192 [[NSNotificationCenter defaultCenter] addObserver:self
193 selector:@selector(pithosNodeChildrenUpdated:)
194 name:@"PithosContainerNodeChildrenUpdated"
196 // PithosSubdirNode updates browser nodes
197 [[NSNotificationCenter defaultCenter] addObserver:self
198 selector:@selector(pithosNodeChildrenUpdated:)
199 name:@"PithosSubdirNodeChildrenUpdated"
201 // PithosAccountNode accountNode updates outlineView container nodes
202 [[NSNotificationCenter defaultCenter] addObserver:self
203 selector:@selector(pithosAccountNodeChildrenUpdated:)
204 name:@"PithosAccountNodeChildrenUpdated"
206 // PithosAccountNode other than accountNode updates nodes
207 [[NSNotificationCenter defaultCenter] addObserver:self
208 selector:@selector(pithosNodeChildrenUpdated:)
209 name:@"PithosAccountNodeChildrenUpdated"
211 // PithosSharingAccountsNode othersSharedNode updates browser nodes
212 [[NSNotificationCenter defaultCenter] addObserver:self
213 selector:@selector(pithosNodeChildrenUpdated:)
214 name:@"PithosSharingAccountsNodeChildrenUpdated"
215 object:othersSharedNode];
216 // Updated authentication credentials reset containers in the outline view
217 [[NSNotificationCenter defaultCenter] addObserver:self
218 selector:@selector(resetContainers:)
219 name:@"PithosAuthenticationCredentialsUpdated"
221 // Request for browser refresh
222 [[NSNotificationCenter defaultCenter] addObserver:self
223 selector:@selector(pithosBrowserRefreshNeeded:)
224 name:@"PithosBrowserRefreshNeeeded"
229 #pragma mark Observers
231 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
232 PithosNode *node = (PithosNode *)[notification object];
233 if (node == accountNode)
235 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
236 NSInteger lastColumn = [browser lastColumn];
237 for (NSInteger column = lastColumn; column >= 0; column--) {
238 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
239 [browser reloadColumn:column];
245 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
246 BOOL containerPithosFound = NO;
247 BOOL containerTrashFound = NO;
248 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
249 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
250 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
251 [removedContainersNodeChildren addIndex:i];
253 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
254 for (PithosContainerNode *containerNode in accountNode.children) {
255 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
256 if (![containersNodeChildren containsObject:containerNode])
257 [containersNodeChildren insertObject:containerNode atIndex:0];
258 containerPithosFound = YES;
259 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
260 NSUInteger insertIndex = 1;
261 if (!containerPithosFound)
263 if (![containersNodeChildren containsObject:containerNode])
264 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
265 containerTrashFound = YES;
266 } else if (![containersNodeChildren containsObject:containerNode]) {
267 [containersNodeChildren addObject:containerNode];
270 BOOL refreshAccountNode = NO;
271 if (!containerPithosFound) {
272 // Create pithos node
273 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
274 [containerRequest startSynchronous];
275 if ([containerRequest error]) {
276 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
278 refreshAccountNode = YES;
281 if (!containerTrashFound) {
283 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
284 [containerRequest startSynchronous];
285 if ([containerRequest error]) {
286 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
288 refreshAccountNode = YES;
292 if (refreshAccountNode)
293 [accountNode refresh];
295 [outlineView reloadData];
297 // Expand the folder outline view
298 [outlineView expandItem:nil expandChildren:YES];
300 if ((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) {
301 rootNode = [containersNodeChildren objectAtIndex:0];
302 [browser loadColumnZero];
309 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
316 - (IBAction)refresh:(id)sender {
317 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
319 [accountNode forceRefresh];
320 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
321 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
322 node.forcedRefresh = YES;
323 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
324 //[(PithosNode *)[browser parentForItemsInColumn:column] forceRefresh];
328 [accountNode refresh];
329 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
330 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
333 [browser validateVisibleColumns];
337 #pragma mark NSBrowserDelegate
339 - (id)rootItemForBrowser:(NSBrowser *)browser {
343 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
344 PithosNode *node = (PithosNode *)item;
345 return node.children.count;
348 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
349 PithosNode *node = (PithosNode *)item;
350 return [node.children objectAtIndex:index];
353 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
354 PithosNode *node = (PithosNode *)item;
355 return node.isLeafItem;
358 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
359 PithosNode *node = (PithosNode *)item;
363 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
364 if (sharedPreviewController == nil)
365 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
366 return sharedPreviewController;
369 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
370 // if (!forUserResize) {
371 // id item = [browser parentForItemsInColumn:columnIndex];
372 // if ([self browser:browser isLeafItem:item]) {
373 // suggestedWidth = 200;
376 // return suggestedWidth;
379 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
385 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
386 PithosNode *node = (PithosNode *)item;
387 if (node.shared || node.sharingAccount ||
388 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
393 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
394 PithosNode *node = (PithosNode *)item;
395 NSString *newName = (NSString *)object;
396 NSUInteger newNameLength = [newName length];
397 NSRange firstSlashRange = [newName rangeOfString:@"/"];
398 if ((newNameLength == 0) ||
399 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
400 ([newName isEqualToString:node.displayName])) {
403 if (([node class] == [PithosObjectNode class]) ||
404 (([node class] == [PithosSubdirNode class]) &&
405 !node.pithosObject.subdir &&
406 [node.pithosObject.name hasSuffix:@"/"])) {
407 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
408 dispatch_async(queue, ^{
409 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
410 if ([newName hasSuffix:@"/"])
411 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
412 NSError *error = nil;
414 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
415 objectName:destinationObjectName
417 isDirectory:&isDirectory
418 sharingAccount:nil]) {
419 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
420 [alert setMessageText:@"Name Taken"];
421 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
422 [alert addButtonWithTitle:@"OK"];
428 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
429 objectName:node.pithosObject.name
430 destinationContainerName:node.pithosContainer.name
431 destinationObjectName:destinationObjectName
434 objectRequest.delegate = self;
435 objectRequest.didFinishSelector = @selector(moveFinished:);
436 objectRequest.didFailSelector = @selector(moveFailed:);
437 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
438 node.parent, @"node",
440 [objectRequest startAsynchronous];
443 } else if ([node class] == [PithosSubdirNode class]) {
444 if (firstSlashRange.length == 1)
446 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
447 dispatch_async(queue, ^{
448 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
449 NSError *error = nil;
451 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
452 objectName:destinationObjectName
454 isDirectory:&isDirectory
455 sharingAccount:nil]) {
456 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
457 [alert setMessageText:@"Name Taken"];
458 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
459 [alert addButtonWithTitle:@"OK"];
465 if (node.pithosObject.subdir)
466 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
467 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
468 objectName:node.pithosObject.name
469 destinationContainerName:node.pithosContainer.name
470 destinationObjectName:destinationObjectName
472 if (objectRequests) {
473 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
474 objectRequest.delegate = self;
475 objectRequest.didFinishSelector = @selector(moveFinished:);
476 objectRequest.didFailSelector = @selector(moveFailed:);
477 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
478 node.parent, @"node",
480 [objectRequest startAsynchronous];
487 #pragma mark Drag and Drop source
489 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
490 withEvent:(NSEvent *)event {
491 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
492 __block BOOL result = YES;
493 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
494 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
495 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
503 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
504 toPasteboard:(NSPasteboard *)pasteboard {
505 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
506 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
507 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
508 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
509 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
510 [propertyList addObject:[node.pithosObject.name pathExtension]];
511 [nodes addObject:node];
514 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
515 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
516 self.draggedNodes = nodes;
517 self.draggedParentNode = [browser parentForItemsInColumn:column];
521 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
522 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
523 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
524 for (PithosNode *node in draggedNodes) {
525 // If the node is a subdir ask if the whole tree should be downloaded
526 if ([node class] == [PithosSubdirNode class]) {
527 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
528 [alert setMessageText:@"Download directory"];
529 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
530 [alert addButtonWithTitle:@"OK"];
531 [alert addButtonWithTitle:@"Cancel"];
532 NSInteger choice = [alert runModal];
533 if (choice == NSAlertFirstButtonReturn) {
534 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
535 dispatch_async(queue, ^{
536 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
537 objectName:node.pithosObject.name
538 toDirectory:[dropDestination path]
540 sharingAccount:node.sharingAccount];
541 if (objectRequests) {
542 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
543 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
544 objectRequest.delegate = self;
545 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
546 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
547 [objectRequest startAsynchronous];
553 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
554 objectName:node.pithosObject.name
555 toDirectory:[dropDestination path]
557 sharingAccount:node.sharingAccount];
559 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
560 objectRequest.delegate = self;
561 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
562 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
563 [objectRequest startAsynchronous];
570 #pragma mark Drag and Drop destination
572 - (NSDragOperation)browser:aBrowser
573 validateDrop:(id<NSDraggingInfo>)info
574 proposedRow:(NSInteger *)row
575 column:(NSInteger *)column
576 dropOperation:(NSBrowserDropOperation *)dropOperation {
577 NSDragOperation result = NSDragOperationNone;
578 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
579 // For a drop above, the drop is redirected to the parent item
580 if (*dropOperation == NSBrowserDropAbove)
582 // Only allow dropping in folders
584 PithosNode *dropNode;
586 // Check if the node is not a folder and if so redirect to the parent item
587 dropNode = [browser itemAtRow:*row inColumn:*column];
588 if ([dropNode class] == [PithosObjectNode class])
592 dropNode = [browser parentForItemsInColumn:*column];
594 if (!dropNode.shared &&
595 (!dropNode.sharingAccount ||
596 ([dropNode class] == [PithosSubdirNode class]) ||
597 ([dropNode class] == [PithosContainerNode class]))) {
598 *dropOperation = NSBrowserDropOn;
599 result = NSDragOperationCopy;
602 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
603 // For a drop above, the drop is redirected to the parent item
604 if (*dropOperation == NSBrowserDropAbove)
606 // Only allow dropping in folders
608 PithosNode *dropNode;
610 // Check if the node is not a folder and if so redirect to the parent item
611 dropNode = [browser itemAtRow:*row inColumn:*column];
612 if ([dropNode class] == [PithosObjectNode class])
616 dropNode = [browser parentForItemsInColumn:*column];
618 if (!dropNode.shared && !dropNode.sharingAccount) {
619 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
620 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
621 if ((([dropNode class] == [PithosContainerNode class]) ||
622 dropNode.pithosObject.subdir ||
623 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
624 ![dropNode isEqualTo:draggedParentNode]) {
625 // ![dropNode isEqualTo:draggedParentNode] &&
626 // ![draggedNodes containsObject:dropNode]) {
627 result = NSDragOperationMove;
629 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
630 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
631 if (([dropNode class] == [PithosContainerNode class]) ||
632 dropNode.pithosObject.subdir ||
633 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
634 result = NSDragOperationCopy;
643 - (BOOL)browser:(NSBrowser *)aBrowser
644 acceptDrop:(id<NSDraggingInfo>)info
646 column:(NSInteger)column
647 dropOperation:(NSBrowserDropOperation)dropOperation {
648 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
649 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
650 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
651 if ((column != -1) && (filenames != nil)) {
652 PithosNode *node = nil;
654 node = [browser itemAtRow:row inColumn:column];
656 node = [browser parentForItemsInColumn:column];
657 NSLog(@"drag in node: %@", node.url);
658 return [self uploadFiles:filenames toNode:node];
660 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
661 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
662 if ((column != -1) && (draggedNodes != nil)) {
663 PithosNode *node = nil;
665 node = [browser itemAtRow:row inColumn:column];
667 node = [browser parentForItemsInColumn:column];
668 NSLog(@"drag local node: %@", node.url);
669 if ([info draggingSourceOperationMask] & NSDragOperationMove)
670 return [self moveNodes:draggedNodes toNode:node];
671 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
672 return [self copyNodes:draggedNodes toNode:node];
679 #pragma mark Drag and Drop methods
681 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
682 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
684 NSFileManager *defaultManager = [NSFileManager defaultManager];
685 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
686 NSString *objectNamePrefix;
687 if ([destinationNode class] == [PithosSubdirNode class])
688 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
690 objectNamePrefix = [NSString string];
691 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
692 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithContainerName:containerName];
693 [containerRequest startSynchronous];
694 if ([containerRequest error]) {
695 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
697 } else if (containerRequest.responseStatusCode != 200) {
698 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
701 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
702 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
704 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
705 NSString *blockHash = destinationNode.pithosContainer.blockHash;
707 for (NSString *filePath in filenames) {
709 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
712 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
713 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
714 dispatch_async(queue, ^{
715 NSError *error = nil;
716 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
717 if (contentType == nil)
718 contentType = @"application/octet-stream";
720 NSLog(@"contentType detection error: %@", error);
721 NSArray *hashes = nil;
722 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
723 objectName:objectName
724 contentType:contentType
730 sharingAccount:destinationNode.sharingAccount];
732 objectRequest.delegate = self;
733 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
734 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
735 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
736 containerName, @"containerName",
737 objectName, @"objectName",
738 contentType, @"contentType",
739 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
740 blockHash, @"blockHash",
741 filePath, @"filePath",
743 destinationNode, @"node",
744 [NSNumber numberWithUnsignedInteger:10], @"iteration",
746 if (destinationNode.sharingAccount)
747 [userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
748 objectRequest.userInfo = userInfo;
749 [objectRequest startAsynchronous];
753 // Upload directory, confirm first
754 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
755 [alert setMessageText:@"Upload directory"];
756 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
757 [alert addButtonWithTitle:@"OK"];
758 [alert addButtonWithTitle:@"Cancel"];
759 NSInteger choice = [alert runModal];
760 if (choice == NSAlertFirstButtonReturn) {
761 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
762 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
763 dispatch_async(queue, ^{
764 NSMutableArray *objectNames = nil;
765 NSMutableArray *contentTypes = nil;
766 NSMutableArray *filePaths = nil;
767 NSMutableArray *hashesArrays = nil;
768 NSMutableArray *directoryObjectRequests = nil;
769 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
770 objectName:objectName
773 forDirectory:filePath
775 objectNames:&objectNames
776 contentTypes:&contentTypes
778 hashesArrays:&hashesArrays
779 directoryObjectRequests:&directoryObjectRequests
780 sharingAccount:destinationNode.sharingAccount];
781 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
782 objectRequest.delegate = self;
783 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
784 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
785 [objectRequest startAsynchronous];
787 if (objectRequests) {
788 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
789 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
790 objectRequest.delegate = self;
791 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
792 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
793 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
794 containerName, @"containerName",
795 [objectNames objectAtIndex:i], @"objectName",
796 [contentTypes objectAtIndex:i], @"contentType",
797 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
798 blockHash, @"blockHash",
799 [filePaths objectAtIndex:i], @"filePath",
800 [hashesArrays objectAtIndex:i], @"hashes",
801 [NSNumber numberWithUnsignedInteger:10], @"iteration",
803 if (destinationNode.sharingAccount)
804 [userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
805 objectRequest.userInfo = userInfo;
806 [objectRequest startAsynchronous];
817 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
818 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
820 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
821 NSString *objectNamePrefix;
822 if ([destinationNode class] == [PithosSubdirNode class])
823 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
825 objectNamePrefix = [NSString string];
827 for (PithosNode *node in nodes) {
828 if (([node class] == [PithosObjectNode class]) ||
829 (([node class] == [PithosSubdirNode class]) &&
830 !node.pithosObject.subdir &&
831 [node.pithosObject.name hasSuffix:@"/"])) {
832 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
833 dispatch_async(queue, ^{
834 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
835 if ([node.pithosObject.name hasSuffix:@"/"])
836 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
837 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
838 objectName:node.pithosObject.name
839 destinationContainerName:containerName
840 destinationObjectName:destinationObjectName
843 objectRequest.delegate = self;
844 objectRequest.didFinishSelector = @selector(moveFinished:);
845 objectRequest.didFailSelector = @selector(moveFailed:);
846 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
847 node.parent, @"node",
848 destinationNode, @"dropNode",
850 [objectRequest startAsynchronous];
853 } else if ([node class] == [PithosSubdirNode class]) {
854 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
855 dispatch_async(queue, ^{
856 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
857 if (node.pithosObject.subdir)
858 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
859 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
860 objectName:node.pithosObject.name
861 destinationContainerName:containerName
862 destinationObjectName:destinationObjectName
864 if (objectRequests) {
865 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
866 objectRequest.delegate = self;
867 objectRequest.didFinishSelector = @selector(moveFinished:);
868 objectRequest.didFailSelector = @selector(moveFailed:);
869 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
870 node.parent, @"node",
871 destinationNode, @"dropNode",
873 [objectRequest startAsynchronous];
882 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
883 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
885 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
886 NSString *objectNamePrefix;
887 if ([destinationNode class] == [PithosSubdirNode class])
888 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
890 objectNamePrefix = [NSString string];
892 for (PithosNode *node in nodes) {
893 if (([node class] == [PithosObjectNode class]) ||
894 (([node class] == [PithosSubdirNode class]) &&
895 !node.pithosObject.subdir &&
896 [node.pithosObject.name hasSuffix:@"/"])) {
897 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
898 dispatch_async(queue, ^{
899 NSString *destinationObjectName;
900 if (![destinationNode isEqualTo:node.parent]) {
901 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
902 if ([node.pithosObject.name hasSuffix:@"/"])
903 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
905 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
906 objectName:node.pithosObject.name];
908 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
909 objectName:node.pithosObject.name
910 destinationContainerName:containerName
911 destinationObjectName:destinationObjectName
915 objectRequest.delegate = self;
916 objectRequest.didFinishSelector = @selector(copyFinished:);
917 objectRequest.didFailSelector = @selector(copyFailed:);
918 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
919 destinationNode, @"dropNode",
921 [objectRequest startAsynchronous];
924 } else if ([node class] == [PithosSubdirNode class]) {
925 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
926 dispatch_async(queue, ^{
927 NSString *destinationObjectName;
928 if (![destinationNode isEqualTo:node.parent]) {
929 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
930 if (node.pithosObject.subdir)
931 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
933 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
934 subdirName:node.pithosObject.name];
936 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
937 objectName:node.pithosObject.name
938 destinationContainerName:containerName
939 destinationObjectName:destinationObjectName
942 if (objectRequests) {
943 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
944 objectRequest.delegate = self;
945 objectRequest.didFinishSelector = @selector(copyFinished:);
946 objectRequest.didFailSelector = @selector(copyFailed:);
947 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
948 destinationNode, @"dropNode",
950 [objectRequest startAsynchronous];
960 #pragma mark ASIHTTPRequestDelegate
962 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
963 NSLog(@"Download completed: %@", [objectRequest url]);
964 if (objectRequest.responseStatusCode == 200) {
965 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
966 NSLog(@"Downloaded 0 bytes");
967 NSFileManager *defaultManager = [NSFileManager defaultManager];
968 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
969 if (![defaultManager fileExistsAtPath:filePath]) {
970 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
971 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
972 [alert setMessageText:@"Create File Error"];
973 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
974 [alert addButtonWithTitle:@"OK"];
980 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
984 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
985 NSLog(@"Download failed");
986 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
989 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
990 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
991 if (objectRequest.responseStatusCode == 201) {
992 NSLog(@"Directory object created: %@", [objectRequest url]);
995 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
999 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1000 NSLog(@"Upload directory object failed");
1001 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1004 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1005 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
1006 if (objectRequest.responseStatusCode == 201) {
1007 NSLog(@"Object created: %@", [objectRequest url]);
1008 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1013 } else if (objectRequest.responseStatusCode == 409) {
1014 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
1015 if (iteration == 0) {
1016 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
1017 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1018 [alert setMessageText:@"Upload Timeout"];
1019 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1020 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
1021 [alert addButtonWithTitle:@"OK"];
1025 NSLog(@"object is missing hashes: %@", [objectRequest url]);
1026 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1027 withMissingHashesResponse:[objectRequest responseString]];
1028 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1029 ASIPithosContainerRequest *newContainerRequest = [PithosFileUtilities updateContainerDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1030 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1031 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1032 missingBlockIndex:missingBlockIndex
1033 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1034 newContainerRequest.delegate = self;
1035 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1036 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1037 newContainerRequest.userInfo = objectRequest.userInfo;
1038 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
1039 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1040 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1041 [newContainerRequest startAsynchronous];
1043 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1047 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
1048 NSLog(@"Upload using hashmap failed");
1049 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1052 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1053 NSLog(@"Upload of missing block completed: %@", [containerRequest url]);
1054 if (containerRequest.responseStatusCode == 202) {
1055 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1056 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1057 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1058 if (missingBlockIndex == NSNotFound) {
1059 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1060 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1061 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1062 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1063 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1064 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1065 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1068 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1069 newObjectRequest.delegate = self;
1070 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1071 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1072 newObjectRequest.userInfo = containerRequest.userInfo;
1073 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1074 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1075 [newObjectRequest startAsynchronous];
1077 ASIPithosContainerRequest *newContainerRequest = [PithosFileUtilities updateContainerDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1078 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1079 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1080 missingBlockIndex:missingBlockIndex
1081 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1082 newContainerRequest.delegate = self;
1083 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1084 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1085 newContainerRequest.userInfo = containerRequest.userInfo;
1086 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1087 [newContainerRequest startAsynchronous];
1090 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1094 - (void)uploadMissingBlockFailed:(ASIPithosContainerRequest *)containerRequest {
1095 NSLog(@"Upload of missing block failed");
1096 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
1099 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1100 NSLog(@"Move object completed: %@", [objectRequest url]);
1101 if (objectRequest.responseStatusCode == 201) {
1102 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1103 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1108 if (!node || !dropNode)
1111 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1115 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1116 NSLog(@"Move object failed");
1117 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1120 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1121 NSLog(@"Copy object completed: %@", [objectRequest url]);
1122 if (objectRequest.responseStatusCode == 201) {
1123 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1129 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1133 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1134 NSLog(@"Copy object failed");
1135 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1139 #pragma mark NSSplitViewDelegate
1141 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1142 if (splitView == verticalSplitView)
1145 return ([horizontalSplitView bounds].size.height - 87);
1148 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1149 if (splitView == verticalSplitView)
1152 return ([horizontalSplitView bounds].size.height - 87);
1155 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1156 if (splitView == verticalSplitView) {
1157 if (proposedPosition < 120)
1159 else if (proposedPosition > 220)
1162 return proposedPosition;
1164 return ([horizontalSplitView bounds].size.height - 87);
1169 #pragma mark NSOutlineViewDataSource
1171 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1174 if (item == containersNode)
1175 return containersNodeChildren.count;
1176 if (item == sharedNode)
1181 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1183 return (!index ? containersNode : sharedNode);
1184 if (item == sharedNode)
1185 return (!index ? mySharedNode : othersSharedNode);
1186 return [containersNodeChildren objectAtIndex:index];
1189 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1190 if ((item == containersNode) || (item == sharedNode))
1195 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1196 PithosNode *node = (PithosNode *)item;
1200 #pragma mark Drag and Drop destination
1202 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
1203 validateDrop:(id<NSDraggingInfo>)info
1204 proposedItem:(id)item
1205 proposedChildIndex:(NSInteger)index {
1206 NSDragOperation result = NSDragOperationNone;
1207 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
1209 PithosNode *dropNode = (PithosNode *)item;
1210 if ([dropNode class] != [PithosContainerNode class])
1212 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1213 result = NSDragOperationCopy;
1214 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1215 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
1216 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
1217 if (![dropNode isEqualTo:draggedParentNode])
1218 result = NSDragOperationMove;
1219 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
1220 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
1221 result = NSDragOperationCopy;
1227 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
1228 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1229 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1230 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1231 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
1232 PithosNode *node = (PithosNode *)item;
1233 NSLog(@"drag in node: %@", node.url);
1234 return [self uploadFiles:filenames toNode:node];
1236 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1237 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1238 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
1239 PithosNode *node = (PithosNode *)item;
1240 NSLog(@"drag local node: %@", node.url);
1241 if ([info draggingSourceOperationMask] & NSDragOperationMove)
1242 return [self moveNodes:draggedNodes toNode:node];
1243 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1244 return [self copyNodes:draggedNodes toNode:node];
1251 #pragma mark NSOutlineViewDelegate
1253 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1254 if ((item == containersNode) || (item == sharedNode))
1259 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1260 if ((item == containersNode) || (item == sharedNode))
1265 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1266 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1269 [browser loadColumnZero];
1275 #pragma mark NSMenuDelegate
1277 - (void)menuNeedsUpdate:(NSMenu *)menu {
1278 [menu removeAllItems];
1279 NSMenuItem *menuItem;
1280 NSString *menuItemTitle;
1281 BOOL nodeContextMenu = NO;
1282 PithosNode *menuNode;
1283 NSMutableArray *menuNodes;
1284 if (menu == browserMenu) {
1285 NSInteger column = [browser clickedColumn];
1286 NSInteger row = [browser clickedRow];
1287 if ((column == -1) || (row == -1)) {
1288 // General context menu
1289 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1290 if ([menuNodesIndexPaths count] == 0) {
1291 menuNode = [browser parentForItemsInColumn:0];
1292 } else if (([menuNodesIndexPaths count] != 1) ||
1293 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1294 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1296 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1299 // Node context menu
1300 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1301 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1302 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1303 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1304 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1305 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1308 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1310 nodeContextMenu = YES;
1312 } else if (menu == outlineViewMenu) {
1313 NSInteger row = [outlineView clickedRow];
1315 row = [outlineView selectedRow];
1318 menuNode = [outlineView itemAtRow:row];
1321 if (!nodeContextMenu) {
1322 // General context menu
1323 if (([menuNode class] == [PithosAccountNode class]) ||
1324 ([menuNode class] == [PithosSharingAccountsNode class]) ||
1325 ([menuNode class] == [PithosEmptyNode class]))
1327 BOOL shared = menuNode.shared;
1328 BOOL sharingAccount = (menuNode.sharingAccount != nil);
1330 if (!shared && !sharingAccount) {
1331 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1332 [menuItem setRepresentedObject:menuNode];
1333 [menu addItem:menuItem];
1334 [menu addItem:[NSMenuItem separatorItem]];
1337 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1338 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1339 [menu addItem:menuItem];
1341 if (!shared && !sharingAccount) {
1342 if (clipboardNodes) {
1343 NSUInteger clipboardNodesCount = [clipboardNodes count];
1344 if (clipboardNodesCount == 0) {
1345 self.clipboardNodes = nil;
1346 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1347 if (clipboardNodesCount == 1)
1348 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1350 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1351 [menu addItem:[NSMenuItem separatorItem]];
1352 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1353 [menuItem setRepresentedObject:menuNode];
1354 [menu addItem:menuItem];
1359 // Node context menu
1360 NSUInteger menuNodesCount = [menuNodes count];
1361 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1362 BOOL shared = firstMenuNode.shared;
1363 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1364 // Move to Trash (pithos container only)
1366 if (!shared && !sharingAccount) {
1367 if ([rootNode class] == [PithosContainerNode class]) {
1368 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1369 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1370 [menuItem setRepresentedObject:menuNodes];
1371 [menu addItem:menuItem];
1373 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1374 [menuItem setRepresentedObject:menuNodes];
1375 [menu addItem:menuItem];
1376 [menu addItem:[NSMenuItem separatorItem]];
1380 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1381 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1382 [menuItem setRepresentedObject:menuNodes];
1383 [menu addItem:menuItem];
1385 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1386 [menu addItem:[NSMenuItem separatorItem]];
1389 if (!shared && !sharingAccount) {
1390 if (menuNodesCount == 1)
1391 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1393 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1394 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1395 [menuItem setRepresentedObject:menuNodes];
1396 [menu addItem:menuItem];
1399 if ((!shared && !sharingAccount) ||
1400 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1401 if (menuNodesCount == 1)
1402 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1404 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1405 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1406 [menuItem setRepresentedObject:menuNodes];
1407 [menu addItem:menuItem];
1410 if (!shared && !sharingAccount) {
1411 if (menuNodesCount == 1) {
1412 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1413 if (([menuNode class] == [PithosSubdirNode class]) &&
1414 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1415 if (clipboardNodes) {
1416 NSUInteger clipboardNodesCount = [clipboardNodes count];
1417 if (clipboardNodesCount == 0) {
1418 self.clipboardNodes = nil;
1419 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1420 if (clipboardNodesCount == 1)
1421 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1423 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1424 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1425 [menuItem setRepresentedObject:menuNode];
1426 [menu addItem:menuItem];
1436 #pragma mark Menu Actions
1438 - (void)menuNewFolder:(NSMenuItem *)sender {
1439 PithosNode *node = (PithosNode *)[sender representedObject];
1440 if ([node class] == [PithosContainerNode class]) {
1441 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1442 dispatch_async(queue, ^{
1443 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1444 subdirName:@"untitled folder"];
1445 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1446 objectName:safeObjectName
1448 contentType:@"application/directory"
1450 contentDisposition:nil
1453 isPublic:ASIPithosObjectRequestPublicIgnore
1455 data:[NSData data]];
1456 objectRequest.delegate = self;
1457 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1458 objectRequest.didFailSelector = @selector(newFolderFailed:);
1459 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1462 [objectRequest startAsynchronous];
1464 } else if (([node class] == [PithosSubdirNode class]) &&
1465 (node.pithosObject.subdir ||
1466 ![node.pithosObject.name hasSuffix:@"/"])) {
1467 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1468 dispatch_async(queue, ^{
1469 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1470 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1471 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1472 objectName:safeObjectName
1474 contentType:@"application/directory"
1476 contentDisposition:nil
1479 isPublic:ASIPithosObjectRequestPublicIgnore
1481 data:[NSData data]];
1482 objectRequest.delegate = self;
1483 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1484 objectRequest.didFailSelector = @selector(newFolderFailed:);
1485 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1488 [objectRequest startAsynchronous];
1493 - (void)menuGetInfo:(NSMenuItem *)sender {
1494 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1495 [node showPithosNodeInfo:sender];
1499 - (void)menuDelete:(NSMenuItem *)sender {
1500 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1501 if (([node class] == [PithosObjectNode class]) ||
1502 (([node class] == [PithosSubdirNode class]) &&
1503 !node.pithosObject.subdir &&
1504 [node.pithosObject.name hasSuffix:@"/"])) {
1505 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1506 objectName:node.pithosObject.name];
1507 objectRequest.delegate = self;
1508 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1509 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1510 [objectRequest startAsynchronous];
1511 } else if ([node class] == [PithosSubdirNode class]) {
1512 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1513 dispatch_async(queue, ^{
1514 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1515 objectName:node.pithosObject.name];
1516 if (objectRequests) {
1517 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1518 objectRequest.delegate = self;
1519 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1520 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1521 [objectRequest startAsynchronous];
1529 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1530 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1531 if (([node class] == [PithosObjectNode class]) ||
1532 (([node class] == [PithosSubdirNode class]) &&
1533 !node.pithosObject.subdir &&
1534 [node.pithosObject.name hasSuffix:@"/"])) {
1535 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1536 dispatch_async(queue, ^{
1537 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1538 objectName:node.pithosObject.name];
1539 if (safeObjectName) {
1540 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1541 objectName:node.pithosObject.name
1542 destinationContainerName:@"trash"
1543 destinationObjectName:safeObjectName
1545 if (objectRequest) {
1546 objectRequest.delegate = self;
1547 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1548 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1549 [objectRequest startAsynchronous];
1553 } else if ([node class] == [PithosSubdirNode class]) {
1554 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1555 dispatch_async(queue, ^{
1556 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1557 subdirName:node.pithosObject.name];
1558 if (safeObjectName) {
1559 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1560 objectName:node.pithosObject.name
1561 destinationContainerName:@"trash"
1562 destinationObjectName:safeObjectName
1564 if (objectRequests) {
1565 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1566 objectRequest.delegate = self;
1567 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1568 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1569 [objectRequest startAsynchronous];
1578 - (void)menuCut:(NSMenuItem *)sender {
1579 self.clipboardNodes = (NSArray *)[sender representedObject];
1580 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1581 self.clipboardCopy = NO;
1584 - (void)menuCopy:(NSMenuItem *)sender {
1585 self.clipboardNodes = (NSArray *)[sender representedObject];
1586 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1587 self.clipboardCopy = YES;
1590 - (void)menuPaste:(NSMenuItem *)sender {
1591 if (!clipboardNodes || ![clipboardNodes count])
1593 PithosNode *dropNode = (PithosNode *)[sender representedObject];
1594 if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) ||
1595 (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"]))
1598 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1599 NSString *objectNamePrefix;
1600 if ([dropNode class] == [PithosSubdirNode class])
1601 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1603 objectNamePrefix = [NSString string];
1605 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1606 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1607 self.clipboardNodes = nil;
1608 self.clipboardParentNode = nil;
1609 for (PithosNode *node in localClipboardNodes) {
1610 if (([node class] == [PithosObjectNode class]) ||
1611 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1612 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1613 dispatch_async(queue, ^{
1614 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1615 if ([node.pithosObject.name hasSuffix:@"/"])
1616 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1617 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1618 objectName:node.pithosObject.name
1619 destinationContainerName:containerName
1620 destinationObjectName:destinationObjectName
1622 if (objectRequest) {
1623 objectRequest.delegate = self;
1624 objectRequest.didFinishSelector = @selector(moveFinished:);
1625 objectRequest.didFailSelector = @selector(moveFailed:);
1626 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1627 node.parent, @"node",
1628 dropNode, @"dropNode",
1630 [objectRequest startAsynchronous];
1633 } else if ([node class] == [PithosSubdirNode class]) {
1634 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1635 dispatch_async(queue, ^{
1636 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1637 if (node.pithosObject.subdir)
1638 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1639 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1640 objectName:node.pithosObject.name
1641 destinationContainerName:containerName
1642 destinationObjectName:destinationObjectName
1644 if (objectRequests) {
1645 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1646 objectRequest.delegate = self;
1647 objectRequest.didFinishSelector = @selector(moveFinished:);
1648 objectRequest.didFailSelector = @selector(moveFailed:);
1649 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1650 node.parent, @"node",
1651 dropNode, @"dropNode",
1653 [objectRequest startAsynchronous];
1660 for (PithosNode *node in localClipboardNodes) {
1661 if (([node class] == [PithosObjectNode class]) ||
1662 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1663 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1664 dispatch_async(queue, ^{
1665 NSString *destinationObjectName;
1666 if (![dropNode isEqualTo:node.parent]) {
1667 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1668 if ([node.pithosObject.name hasSuffix:@"/"])
1669 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1671 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
1672 objectName:node.pithosObject.name];
1674 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1675 objectName:node.pithosObject.name
1676 destinationContainerName:containerName
1677 destinationObjectName:destinationObjectName
1679 sharingAccount:node.sharingAccount];
1680 if (objectRequest) {
1681 objectRequest.delegate = self;
1682 objectRequest.didFinishSelector = @selector(copyFinished:);
1683 objectRequest.didFailSelector = @selector(copyFailed:);
1684 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1685 dropNode, @"dropNode",
1687 [objectRequest startAsynchronous];
1690 } else if ([node class] == [PithosSubdirNode class]) {
1691 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1692 dispatch_async(queue, ^{
1693 NSString *destinationObjectName;
1694 if (![dropNode isEqualTo:node.parent]) {
1695 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1696 if (node.pithosObject.subdir)
1697 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1699 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
1700 subdirName:node.pithosObject.name];
1702 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1703 objectName:node.pithosObject.name
1704 destinationContainerName:containerName
1705 destinationObjectName:destinationObjectName
1707 sharingAccount:node.sharingAccount];
1708 if (objectRequests) {
1709 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1710 objectRequest.delegate = self;
1711 objectRequest.didFinishSelector = @selector(copyFinished:);
1712 objectRequest.didFailSelector = @selector(copyFailed:);
1713 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1714 dropNode, @"dropNode",
1716 [objectRequest startAsynchronous];
1726 #pragma mark Menu Actions ASIHTTPRequestDelegate
1728 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1729 if (objectRequest.responseStatusCode == 201) {
1730 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1731 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1737 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1741 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1742 NSLog(@"Creation of new application/directory object failed");
1743 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1746 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1747 if (objectRequest.responseStatusCode == 204) {
1748 NSLog(@"Object deleted: %@", [objectRequest url]);
1751 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1755 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1756 NSLog(@"Delete of object failed");
1757 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1760 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1761 if (objectRequest.responseStatusCode == 201) {
1762 NSLog(@"Object moved: %@", [objectRequest url]);
1765 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1769 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1770 NSLog(@"Move of object failed");
1771 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];