2 // PithosBrowserController.m
5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosEmptyNode.h"
45 #import "ImageAndTextCell.h"
46 #import "FileSystemBrowserCell.h"
47 #import "ASIPithosRequest.h"
48 #import "ASIPithosContainerRequest.h"
49 #import "ASIPithosObjectRequest.h"
50 #import "ASIPithosContainer.h"
51 #import "ASIPithosObject.h"
52 #import "PithosFileUtilities.h"
54 @interface PithosBrowserCell : FileSystemBrowserCell {}
57 @implementation PithosBrowserCell
60 if ((self = [super init])) {
61 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
62 [self setEditable:YES];
67 - (void)setObjectValue:(id)object {
68 if ([object isKindOfClass:[PithosNode class]]) {
69 PithosNode *node = (PithosNode *)object;
70 [self setStringValue:node.displayName];
71 [self setImage:node.icon];
73 [super setObjectValue:object];
79 @interface PithosOutlineViewCell : ImageAndTextCell {}
82 @implementation PithosOutlineViewCell
84 - (void)setObjectValue:(id)object {
85 if ([object isKindOfClass:[PithosNode class]]) {
86 PithosNode *node = (PithosNode *)object;
87 [self setStringValue:node.displayName];
88 [self setImage:node.icon];
89 [self setEditable:NO];
91 [super setObjectValue:object];
97 @interface PithosBrowserController (Private) {}
98 - (void)resetContainers:(NSNotification *)notification;
101 @implementation PithosBrowserController
102 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser;
103 @synthesize draggedNodes, draggedParentNode;
104 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
107 #pragma Object Lifecycle
110 return [super initWithWindowNibName:@"PithosBrowserController"];
114 [[NSNotificationCenter defaultCenter] removeObserver:self];
115 [clipboardParentNode release];
116 [clipboardNodes release];
117 [draggedParentNode release];
118 [draggedNodes release];
119 [browserMenu release];
120 [sharedPreviewController release];
121 [othersSharedNode release];
122 [mySharedNode release];
123 [sharedNode release];
124 [containersNodeChildren release];
125 [containersNode release];
126 [accountNode release];
131 - (void)awakeFromNib {
132 [super awakeFromNib];
134 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
135 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
136 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
138 [browser setCellClass:[PithosBrowserCell class]];
140 browserMenu = [[NSMenu alloc] init];
141 [browserMenu setDelegate:self];
142 [browser setMenu:browserMenu];
145 - (void)resetContainers:(NSNotification *)notification {
147 [browser loadColumnZero];
148 [outlineView reloadData];
150 // Expand the folder outline view
151 [outlineView expandItem:nil expandChildren:YES];
152 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
155 [accountNode refresh];
158 - (void)windowDidLoad {
159 [super windowDidLoad];
161 accountNode = [[PithosAccountNode alloc] init];
162 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
163 containersNodeChildren = [[NSMutableArray alloc] init];
164 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
165 mySharedNode = [[PithosAccountNode alloc] init];
166 mySharedNode.displayName = @"my shared";
167 mySharedNode.shared = YES;
168 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
169 othersSharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
170 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]];
172 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
174 // Register for updates
175 [[NSNotificationCenter defaultCenter] addObserver:self
176 selector:@selector(pithosNodeChildrenUpdated:)
177 name:@"PithosContainerNodeChildrenUpdated"
179 [[NSNotificationCenter defaultCenter] addObserver:self
180 selector:@selector(pithosNodeChildrenUpdated:)
181 name:@"PithosSubdirNodeChildrenUpdated"
183 [[NSNotificationCenter defaultCenter] addObserver:self
184 selector:@selector(pithosAccountNodeChildrenUpdated:)
185 name:@"PithosAccountNodeChildrenUpdated"
187 [[NSNotificationCenter defaultCenter] addObserver:self
188 selector:@selector(pithosNodeChildrenUpdated:)
189 name:@"PithosAccountNodeChildrenUpdated"
190 object:mySharedNode];
191 [[NSNotificationCenter defaultCenter] addObserver:self
192 selector:@selector(resetContainers:)
193 name:@"PithosAuthenticationCredentialsUpdated"
195 [[NSNotificationCenter defaultCenter] addObserver:self
196 selector:@selector(pithosBrowserRefreshNeeded:)
197 name:@"PithosBrowserRefreshNeeeded"
202 #pragma mark Observers
204 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
205 PithosNode *node = (PithosNode *)[notification object];
206 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
207 NSInteger lastColumn = [browser lastColumn];
208 for (NSInteger column = lastColumn; column >= 0; column--) {
209 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
210 [browser reloadColumn:column];
216 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
217 BOOL containerPithosFound = NO;
218 BOOL containerTrashFound = NO;
219 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
220 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
221 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
222 [removedContainersNodeChildren addIndex:i];
224 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
225 for (PithosContainerNode *containerNode in accountNode.children) {
226 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
227 if (![containersNodeChildren containsObject:containerNode])
228 [containersNodeChildren insertObject:containerNode atIndex:0];
229 containerPithosFound = YES;
230 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
231 NSUInteger insertIndex = 1;
232 if (!containerPithosFound)
234 if (![containersNodeChildren containsObject:containerNode])
235 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
236 containerTrashFound = YES;
237 } else if (![containersNodeChildren containsObject:containerNode]) {
238 [containersNodeChildren addObject:containerNode];
241 BOOL refreshAccountNode = NO;
242 if (!containerPithosFound) {
243 // Create pithos node
244 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
245 [containerRequest startSynchronous];
246 if ([containerRequest error]) {
247 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
249 refreshAccountNode = YES;
252 if (!containerTrashFound) {
254 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
255 [containerRequest startSynchronous];
256 if ([containerRequest error]) {
257 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
259 refreshAccountNode = YES;
263 if (refreshAccountNode)
264 [accountNode refresh];
266 [outlineView reloadData];
268 // Expand the folder outline view
269 [outlineView expandItem:nil expandChildren:YES];
271 if ((rootNode == containersNode) || (rootNode == sharedNode)) {
272 rootNode = [containersNodeChildren objectAtIndex:0];
273 [browser loadColumnZero];
280 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
287 - (IBAction)refresh:(id)sender {
289 [accountNode refresh];
290 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
291 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
293 [browser validateVisibleColumns];
297 #pragma mark NSBrowserDelegate
299 - (id)rootItemForBrowser:(NSBrowser *)browser {
303 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
304 PithosNode *node = (PithosNode *)item;
305 return node.children.count;
308 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
309 PithosNode *node = (PithosNode *)item;
310 return [node.children objectAtIndex:index];
313 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
314 PithosNode *node = (PithosNode *)item;
315 return node.isLeafItem;
318 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
319 PithosNode *node = (PithosNode *)item;
323 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
324 if (sharedPreviewController == nil)
325 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
326 return sharedPreviewController;
329 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
330 // if (!forUserResize) {
331 // id item = [browser parentForItemsInColumn:columnIndex];
332 // if ([self browser:browser isLeafItem:item]) {
333 // suggestedWidth = 200;
336 // return suggestedWidth;
339 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
345 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
346 PithosNode *node = (PithosNode *)item;
347 if (node.shared || ([node class] == [PithosContainerNode class]))
352 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
353 PithosNode *node = (PithosNode *)item;
354 NSString *newName = (NSString *)object;
355 NSUInteger newNameLength = [newName length];
356 NSRange firstSlashRange = [newName rangeOfString:@"/"];
357 if ((newNameLength == 0) ||
358 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
359 ([newName isEqualToString:node.displayName])) {
362 if (([node class] == [PithosObjectNode class]) ||
363 (([node class] == [PithosSubdirNode class]) &&
364 !node.pithosObject.subdir &&
365 [node.pithosObject.name hasSuffix:@"/"])) {
366 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
367 dispatch_async(queue, ^{
368 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
369 if ([newName hasSuffix:@"/"])
370 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
371 NSError *error = nil;
373 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
374 objectName:destinationObjectName
376 isDirectory:&isDirectory]) {
377 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
378 [alert setMessageText:@"Name Taken"];
379 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
380 [alert addButtonWithTitle:@"OK"];
386 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
387 objectName:node.pithosObject.name
388 destinationContainerName:node.pithosContainer.name
389 destinationObjectName:destinationObjectName
392 objectRequest.delegate = self;
393 objectRequest.didFinishSelector = @selector(moveFinished:);
394 objectRequest.didFailSelector = @selector(moveFailed:);
395 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
396 node.parent, @"node",
398 [objectRequest startAsynchronous];
401 } else if ([node class] == [PithosSubdirNode class]) {
402 if (firstSlashRange.length == 1)
404 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
405 dispatch_async(queue, ^{
406 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
407 NSError *error = nil;
409 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
410 objectName:destinationObjectName
412 isDirectory:&isDirectory]) {
413 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
414 [alert setMessageText:@"Name Taken"];
415 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
416 [alert addButtonWithTitle:@"OK"];
422 if (node.pithosObject.subdir)
423 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
424 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
425 objectName:node.pithosObject.name
426 destinationContainerName:node.pithosContainer.name
427 destinationObjectName:destinationObjectName
429 if (objectRequests) {
430 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
431 objectRequest.delegate = self;
432 objectRequest.didFinishSelector = @selector(moveFinished:);
433 objectRequest.didFailSelector = @selector(moveFailed:);
434 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
435 node.parent, @"node",
437 [objectRequest startAsynchronous];
444 #pragma mark Drag and Drop source
446 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
447 withEvent:(NSEvent *)event {
448 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
449 __block BOOL result = YES;
450 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
451 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
452 if ([node class] == [PithosContainerNode class]) {
460 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
461 toPasteboard:(NSPasteboard *)pasteboard {
462 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
463 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
464 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
465 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
466 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
467 [propertyList addObject:[node.pithosObject.name pathExtension]];
468 [nodes addObject:node];
471 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
472 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
473 self.draggedNodes = nodes;
474 self.draggedParentNode = [browser parentForItemsInColumn:column];
478 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
479 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
480 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
481 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
482 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
483 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
485 // If the node is a subdir ask if the whole tree should be downloaded
486 if ([node class] == [PithosSubdirNode class]) {
487 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
488 [alert setMessageText:@"Download directory"];
489 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
490 [alert addButtonWithTitle:@"OK"];
491 [alert addButtonWithTitle:@"Cancel"];
492 NSInteger choice = [alert runModal];
493 if (choice == NSAlertFirstButtonReturn) {
494 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
495 dispatch_async(queue, ^{
496 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
497 objectName:node.pithosObject.name
498 toDirectory:[dropDestination path]
500 if (objectRequests) {
501 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
502 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
503 objectRequest.delegate = self;
504 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
505 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
506 [objectRequest startAsynchronous];
512 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
513 objectName:node.pithosObject.name
514 toDirectory:[dropDestination path]
517 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
518 objectRequest.delegate = self;
519 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
520 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
521 [objectRequest startAsynchronous];
528 #pragma mark Drag and Drop destination
530 - (NSDragOperation)browser:aBrowser
531 validateDrop:(id<NSDraggingInfo>)info
532 proposedRow:(NSInteger *)row
533 column:(NSInteger *)column
534 dropOperation:(NSBrowserDropOperation *)dropOperation {
535 NSDragOperation result = NSDragOperationNone;
536 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
537 // For a drop above, the drop is redirected to the parent item
538 if (*dropOperation == NSBrowserDropAbove)
540 // Only allow dropping in folders
543 // Check if the node is not a folder and if so redirect to the parent item
544 if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class])
547 *dropOperation = NSBrowserDropOn;
548 result = NSDragOperationCopy;
550 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
551 // For a drop above, the drop is redirected to the parent item
552 if (*dropOperation == NSBrowserDropAbove)
554 // Only allow dropping in folders
556 PithosNode *dropNode;
558 // Check if the node is not a folder and if so redirect to the parent item
559 dropNode = [browser itemAtRow:*row inColumn:*column];
560 if ([dropNode class] != [PithosSubdirNode class])
564 dropNode = [browser parentForItemsInColumn:*column];
566 if (!dropNode.shared) {
567 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
568 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
569 if ((([dropNode class] == [PithosContainerNode class]) ||
570 dropNode.pithosObject.subdir ||
571 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
572 ![dropNode isEqualTo:draggedParentNode]) {
573 // ![dropNode isEqualTo:draggedParentNode] &&
574 // ![draggedNodes containsObject:dropNode]) {
575 result = NSDragOperationMove;
577 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
578 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
579 if (([dropNode class] == [PithosContainerNode class]) ||
580 dropNode.pithosObject.subdir ||
581 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
582 result = NSDragOperationCopy;
591 - (BOOL)browser:(NSBrowser *)aBrowser
592 acceptDrop:(id<NSDraggingInfo>)info
594 column:(NSInteger)column
595 dropOperation:(NSBrowserDropOperation)dropOperation {
596 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
597 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
598 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
599 if ((column != -1) && (filenames != nil)) {
600 PithosNode *node = nil;
602 node = [browser itemAtRow:row inColumn:column];
604 node = [browser parentForItemsInColumn:column];
605 NSLog(@"drag in node: %@", node.url);
606 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
609 NSFileManager *defaultManager = [NSFileManager defaultManager];
610 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
611 NSString *objectNamePrefix;
612 if ([node class] == [PithosSubdirNode class])
613 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
615 objectNamePrefix = [NSString string];
616 NSUInteger blockSize = node.pithosContainer.blockSize;
617 NSString *blockHash = node.pithosContainer.blockHash;
619 for (NSString *filePath in filenames) {
621 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
624 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
625 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
626 dispatch_async(queue, ^{
627 NSError *error = nil;
628 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
629 if (contentType == nil)
630 contentType = @"application/octet-stream";
632 NSLog(@"contentType detection error: %@", error);
633 NSArray *hashes = nil;
634 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
635 objectName:objectName
636 contentType:contentType
643 objectRequest.delegate = self;
644 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
645 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
646 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
647 containerName, @"containerName",
648 objectName, @"objectName",
649 contentType, @"contentType",
650 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
651 blockHash, @"blockHash",
652 filePath, @"filePath",
655 [NSNumber numberWithUnsignedInteger:10], @"iteration",
657 [objectRequest startAsynchronous];
661 // Upload directory, confirm first
662 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
663 [alert setMessageText:@"Upload directory"];
664 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
665 [alert addButtonWithTitle:@"OK"];
666 [alert addButtonWithTitle:@"Cancel"];
667 NSInteger choice = [alert runModal];
668 if (choice == NSAlertFirstButtonReturn) {
669 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
670 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
671 dispatch_async(queue, ^{
672 NSMutableArray *objectNames = nil;
673 NSMutableArray *contentTypes = nil;
674 NSMutableArray *filePaths = nil;
675 NSMutableArray *hashesArrays = nil;
676 NSMutableArray *directoryObjectRequests = nil;
677 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
678 objectName:objectName
681 forDirectory:filePath
683 objectNames:&objectNames
684 contentTypes:&contentTypes
686 hashesArrays:&hashesArrays
687 directoryObjectRequests:&directoryObjectRequests];
688 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
689 objectRequest.delegate = self;
690 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
691 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
692 [objectRequest startAsynchronous];
694 if (objectRequests) {
695 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
696 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
697 objectRequest.delegate = self;
698 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
699 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
700 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
701 containerName, @"containerName",
702 [objectNames objectAtIndex:i], @"objectName",
703 [contentTypes objectAtIndex:i], @"contentType",
704 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
705 blockHash, @"blockHash",
706 [filePaths objectAtIndex:i], @"filePath",
707 [hashesArrays objectAtIndex:i], @"hashes",
708 [NSNumber numberWithUnsignedInteger:10], @"iteration",
710 [objectRequest startAsynchronous];
721 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
722 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
723 if ((column != -1) && (draggedNodes != nil)) {
724 PithosNode *dropNode = nil;
726 dropNode = [browser itemAtRow:row inColumn:column];
728 dropNode = [browser parentForItemsInColumn:column];
729 NSLog(@"drag local node: %@", dropNode.url);
730 if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
733 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
734 NSString *objectNamePrefix;
735 if ([dropNode class] == [PithosSubdirNode class])
736 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
738 objectNamePrefix = [NSString string];
740 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
741 for (PithosNode *node in draggedNodes) {
742 if (([node class] == [PithosObjectNode class]) ||
743 (([node class] == [PithosSubdirNode class]) &&
744 !node.pithosObject.subdir &&
745 [node.pithosObject.name hasSuffix:@"/"])) {
746 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
747 dispatch_async(queue, ^{
748 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
749 if ([node.pithosObject.name hasSuffix:@"/"])
750 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
751 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
752 objectName:node.pithosObject.name
753 destinationContainerName:containerName
754 destinationObjectName:destinationObjectName
757 objectRequest.delegate = self;
758 objectRequest.didFinishSelector = @selector(moveFinished:);
759 objectRequest.didFailSelector = @selector(moveFailed:);
760 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
761 node.parent, @"node",
762 dropNode, @"dropNode",
764 [objectRequest startAsynchronous];
767 } else if ([node class] == [PithosSubdirNode class]) {
768 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
769 dispatch_async(queue, ^{
770 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
771 if (node.pithosObject.subdir)
772 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
773 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
774 objectName:node.pithosObject.name
775 destinationContainerName:containerName
776 destinationObjectName:destinationObjectName
778 if (objectRequests) {
779 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
780 objectRequest.delegate = self;
781 objectRequest.didFinishSelector = @selector(moveFinished:);
782 objectRequest.didFailSelector = @selector(moveFailed:);
783 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
784 node.parent, @"node",
785 dropNode, @"dropNode",
787 [objectRequest startAsynchronous];
793 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
794 for (PithosNode *node in draggedNodes) {
795 if (([node class] == [PithosObjectNode class]) ||
796 (([node class] == [PithosSubdirNode class]) &&
797 !node.pithosObject.subdir &&
798 [node.pithosObject.name hasSuffix:@"/"])) {
799 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
800 dispatch_async(queue, ^{
801 NSString *destinationObjectName;
802 if (![dropNode isEqualTo:node.parent]) {
803 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
804 if ([node.pithosObject.name hasSuffix:@"/"])
805 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
807 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
808 objectName:node.pithosObject.name];
810 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
811 objectName:node.pithosObject.name
812 destinationContainerName:containerName
813 destinationObjectName:destinationObjectName
816 objectRequest.delegate = self;
817 objectRequest.didFinishSelector = @selector(copyFinished:);
818 objectRequest.didFailSelector = @selector(copyFailed:);
819 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
820 dropNode, @"dropNode",
822 [objectRequest startAsynchronous];
825 } else if ([node class] == [PithosSubdirNode class]) {
826 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
827 dispatch_async(queue, ^{
828 NSString *destinationObjectName;
829 if (![dropNode isEqualTo:node.parent]) {
830 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
831 if (node.pithosObject.subdir)
832 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
834 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
835 subdirName:node.pithosObject.name];
837 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
838 objectName:node.pithosObject.name
839 destinationContainerName:containerName
840 destinationObjectName:destinationObjectName
842 if (objectRequests) {
843 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
844 objectRequest.delegate = self;
845 objectRequest.didFinishSelector = @selector(copyFinished:);
846 objectRequest.didFailSelector = @selector(copyFailed:);
847 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
848 dropNode, @"dropNode",
850 [objectRequest startAsynchronous];
865 #pragma mark ASIHTTPRequestDelegate
867 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
868 NSLog(@"Download completed: %@", [objectRequest url]);
869 if (objectRequest.responseStatusCode == 200) {
870 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
871 NSLog(@"Downloaded 0 bytes");
872 NSFileManager *defaultManager = [NSFileManager defaultManager];
873 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
874 if (![defaultManager fileExistsAtPath:filePath]) {
875 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
876 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
877 [alert setMessageText:@"Create File Error"];
878 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
879 [alert addButtonWithTitle:@"OK"];
885 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
889 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
890 NSLog(@"Download failed");
891 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
894 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
895 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
896 if (objectRequest.responseStatusCode == 201) {
897 NSLog(@"Directory object created: %@", [objectRequest url]);
900 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
904 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
905 NSLog(@"Upload directory object failed");
906 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
909 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
910 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
911 if (objectRequest.responseStatusCode == 201) {
912 NSLog(@"Object created: %@", [objectRequest url]);
913 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
918 } else if (objectRequest.responseStatusCode == 409) {
919 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
920 if (iteration == 0) {
921 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
922 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
923 [alert setMessageText:@"Upload Timeout"];
924 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
925 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
926 [alert addButtonWithTitle:@"OK"];
930 NSLog(@"object is missing hashes: %@", [objectRequest url]);
931 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
932 withMissingHashesResponse:[objectRequest responseString]];
933 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
934 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
935 objectName:@".upload"
936 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
937 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
938 missingBlockIndex:missingBlockIndex];
939 newObjectRequest.delegate = self;
940 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
941 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
942 newObjectRequest.userInfo = objectRequest.userInfo;
943 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
944 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
945 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
946 [newObjectRequest startAsynchronous];
948 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
952 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
953 NSLog(@"Upload using hashmap failed");
954 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
957 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
958 NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
959 if (objectRequest.responseStatusCode == 201) {
960 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
961 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
962 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
963 if (missingBlockIndex == NSNotFound) {
964 NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
965 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
966 objectName:[objectRequest.userInfo objectForKey:@"objectName"]
967 contentType:[objectRequest.userInfo objectForKey:@"contentType"]
968 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
969 blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
970 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
973 newObjectRequest.delegate = self;
974 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
975 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
976 newObjectRequest.userInfo = objectRequest.userInfo;
977 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
978 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
979 [newObjectRequest startAsynchronous];
981 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
982 objectName:@".upload"
983 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
984 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
985 missingBlockIndex:missingBlockIndex];
986 newObjectRequest.delegate = self;
987 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
988 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
989 newObjectRequest.userInfo = objectRequest.userInfo;
990 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
991 [newObjectRequest startAsynchronous];
994 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
998 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
999 NSLog(@"Upload of missing block failed");
1000 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1003 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1004 NSLog(@"Move object completed: %@", [objectRequest url]);
1005 if (objectRequest.responseStatusCode == 201) {
1006 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1007 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1012 if (!node || !dropNode)
1015 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1019 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1020 NSLog(@"Move object failed");
1021 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1024 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1025 NSLog(@"Copy object completed: %@", [objectRequest url]);
1026 if (objectRequest.responseStatusCode == 201) {
1027 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1033 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1037 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1038 NSLog(@"Copy object failed");
1039 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1043 #pragma mark NSSplitViewDelegate
1045 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1046 if (splitView == verticalSplitView)
1049 return ([horizontalSplitView bounds].size.height - 87);
1052 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1053 if (splitView == verticalSplitView)
1056 return ([horizontalSplitView bounds].size.height - 87);
1059 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1060 if (splitView == verticalSplitView) {
1061 if (proposedPosition < 120)
1063 else if (proposedPosition > 220)
1066 return proposedPosition;
1068 return ([horizontalSplitView bounds].size.height - 87);
1073 #pragma mark NSOutlineViewDataSource
1075 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1078 if (item == containersNode)
1079 return containersNodeChildren.count;
1080 if (item == sharedNode)
1085 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1087 return (!index ? containersNode : sharedNode);
1088 if (item == sharedNode)
1089 return (!index ? mySharedNode : othersSharedNode);
1090 return [containersNodeChildren objectAtIndex:index];
1093 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1094 if ((item == containersNode) || (item == sharedNode))
1099 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1100 PithosNode *node = (PithosNode *)item;
1105 #pragma mark NSOutlineViewDelegate
1107 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1108 if ((item == containersNode) || (item == sharedNode))
1113 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1114 if ((item == containersNode) || (item == sharedNode))
1119 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1120 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1123 [browser loadColumnZero];
1129 #pragma mark NSMenuDelegate
1131 - (void)menuNeedsUpdate:(NSMenu *)menu {
1132 NSInteger column = [browser clickedColumn];
1133 NSInteger row = [browser clickedRow];
1134 [menu removeAllItems];
1135 NSMenuItem *menuItem;
1136 NSString *menuItemTitle;
1137 if ((column == -1) || (row == -1)) {
1138 // General context menu
1139 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1140 PithosNode *menuNode;
1141 if ([menuNodesIndexPaths count] == 0) {
1142 menuNode = [browser parentForItemsInColumn:0];
1143 } else if (([menuNodesIndexPaths count] != 1) ||
1144 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1145 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1147 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1149 if ([menuNode class] == [PithosAccountNode class])
1151 BOOL shared = menuNode.shared;
1155 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1156 [menuItem setRepresentedObject:menuNode];
1157 [menu addItem:menuItem];
1158 [menu addItem:[NSMenuItem separatorItem]];
1162 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1163 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1164 [menu addItem:menuItem];
1168 if (clipboardNodes) {
1169 NSUInteger clipboardNodesCount = [clipboardNodes count];
1170 if (clipboardNodesCount == 0) {
1171 self.clipboardNodes = nil;
1172 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1173 if (clipboardNodesCount == 1)
1174 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1176 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1177 [menu addItem:[NSMenuItem separatorItem]];
1178 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1179 [menuItem setRepresentedObject:menuNode];
1180 [menu addItem:menuItem];
1185 // Node context menu
1186 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1187 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1188 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1189 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1190 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1191 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1194 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1196 NSUInteger menuNodesCount = [menuNodes count];
1197 BOOL shared = ((PithosNode *)[menuNodes objectAtIndex:0]).shared;
1201 // Move to Trash (pithos container only)
1203 if ([rootNode class] == [PithosContainerNode class]) {
1204 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1205 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1206 [menuItem setRepresentedObject:menuNodes];
1207 [menu addItem:menuItem];
1209 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1210 [menuItem setRepresentedObject:menuNodes];
1211 [menu addItem:menuItem];
1212 [menu addItem:[NSMenuItem separatorItem]];
1217 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1218 [menuItem setRepresentedObject:menuNodes];
1219 [menu addItem:menuItem];
1221 if (!shared || ([[menuNodes objectAtIndex:0] class] != [PithosContainerNode class]))
1222 [menu addItem:[NSMenuItem separatorItem]];
1227 if (menuNodesCount == 1)
1228 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1230 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1231 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1232 [menuItem setRepresentedObject:menuNodes];
1233 [menu addItem:menuItem];
1236 if (!shared || ([[menuNodes objectAtIndex:0] class] != [PithosContainerNode class])) {
1238 if (menuNodesCount == 1)
1239 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1241 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1242 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1243 [menuItem setRepresentedObject:menuNodes];
1244 [menu addItem:menuItem];
1249 if (menuNodesCount == 1) {
1250 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1251 if (([menuNode class] == [PithosSubdirNode class]) &&
1252 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1253 if (clipboardNodes) {
1254 NSUInteger clipboardNodesCount = [clipboardNodes count];
1255 if (clipboardNodesCount == 0) {
1256 self.clipboardNodes = nil;
1257 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1258 if (clipboardNodesCount == 1)
1259 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1261 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1262 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1263 [menuItem setRepresentedObject:menuNode];
1264 [menu addItem:menuItem];
1274 #pragma mark Menu Actions
1276 - (void)menuNewFolder:(NSMenuItem *)sender {
1277 PithosNode *node = (PithosNode *)[sender representedObject];
1278 if ([node class] == [PithosContainerNode class]) {
1279 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1280 dispatch_async(queue, ^{
1281 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1282 subdirName:@"untitled folder"];
1283 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1284 objectName:safeObjectName
1286 contentType:@"application/directory"
1288 contentDisposition:nil
1291 isPublic:ASIPithosObjectRequestPublicIgnore
1293 data:[NSData data]];
1294 objectRequest.delegate = self;
1295 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1296 objectRequest.didFailSelector = @selector(newFolderFailed:);
1297 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1300 [objectRequest startAsynchronous];
1302 } else if (([node class] == [PithosSubdirNode class]) &&
1303 (node.pithosObject.subdir ||
1304 ![node.pithosObject.name hasSuffix:@"/"])) {
1305 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1306 dispatch_async(queue, ^{
1307 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1308 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1309 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1310 objectName:safeObjectName
1312 contentType:@"application/directory"
1314 contentDisposition:nil
1317 isPublic:ASIPithosObjectRequestPublicIgnore
1319 data:[NSData data]];
1320 objectRequest.delegate = self;
1321 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1322 objectRequest.didFailSelector = @selector(newFolderFailed:);
1323 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1326 [objectRequest startAsynchronous];
1331 - (void)menuGetInfo:(NSMenuItem *)sender {
1332 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1333 [node showPithosNodeInfo:sender];
1337 - (void)menuDelete:(NSMenuItem *)sender {
1338 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1339 if (([node class] == [PithosObjectNode class]) ||
1340 (([node class] == [PithosSubdirNode class]) &&
1341 !node.pithosObject.subdir &&
1342 [node.pithosObject.name hasSuffix:@"/"])) {
1343 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1344 objectName:node.pithosObject.name];
1345 objectRequest.delegate = self;
1346 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1347 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1348 [objectRequest startAsynchronous];
1349 } else if ([node class] == [PithosSubdirNode class]) {
1350 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1351 dispatch_async(queue, ^{
1352 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1353 objectName:node.pithosObject.name];
1354 if (objectRequests) {
1355 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1356 objectRequest.delegate = self;
1357 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1358 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1359 [objectRequest startAsynchronous];
1367 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1368 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1369 if (([node class] == [PithosObjectNode class]) ||
1370 (([node class] == [PithosSubdirNode class]) &&
1371 !node.pithosObject.subdir &&
1372 [node.pithosObject.name hasSuffix:@"/"])) {
1373 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1374 dispatch_async(queue, ^{
1375 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1376 objectName:node.pithosObject.name];
1377 if (safeObjectName) {
1378 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1379 objectName:node.pithosObject.name
1380 destinationContainerName:@"trash"
1381 destinationObjectName:safeObjectName
1383 if (objectRequest) {
1384 objectRequest.delegate = self;
1385 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1386 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1387 [objectRequest startAsynchronous];
1391 } else if ([node class] == [PithosSubdirNode class]) {
1392 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1393 dispatch_async(queue, ^{
1394 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1395 subdirName:node.pithosObject.name];
1396 if (safeObjectName) {
1397 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1398 objectName:node.pithosObject.name
1399 destinationContainerName:@"trash"
1400 destinationObjectName:safeObjectName
1402 if (objectRequests) {
1403 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1404 objectRequest.delegate = self;
1405 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1406 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1407 [objectRequest startAsynchronous];
1416 - (void)menuCut:(NSMenuItem *)sender {
1417 self.clipboardNodes = (NSArray *)[sender representedObject];
1418 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1419 self.clipboardCopy = NO;
1422 - (void)menuCopy:(NSMenuItem *)sender {
1423 self.clipboardNodes = (NSArray *)[sender representedObject];
1424 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1425 self.clipboardCopy = YES;
1428 - (void)menuPaste:(NSMenuItem *)sender {
1429 if (!clipboardNodes || ![clipboardNodes count])
1431 PithosNode *dropNode = (PithosNode *)[sender representedObject];
1432 if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) ||
1433 (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"]))
1436 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1437 NSString *objectNamePrefix;
1438 if ([dropNode class] == [PithosSubdirNode class])
1439 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1441 objectNamePrefix = [NSString string];
1443 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1444 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1445 self.clipboardNodes = nil;
1446 self.clipboardParentNode = nil;
1447 for (PithosNode *node in localClipboardNodes) {
1448 if (([node class] == [PithosObjectNode class]) ||
1449 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1450 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1451 dispatch_async(queue, ^{
1452 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1453 if ([node.pithosObject.name hasSuffix:@"/"])
1454 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1455 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1456 objectName:node.pithosObject.name
1457 destinationContainerName:containerName
1458 destinationObjectName:destinationObjectName
1460 if (objectRequest) {
1461 objectRequest.delegate = self;
1462 objectRequest.didFinishSelector = @selector(moveFinished:);
1463 objectRequest.didFailSelector = @selector(moveFailed:);
1464 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1465 node.parent, @"node",
1466 dropNode, @"dropNode",
1468 [objectRequest startAsynchronous];
1471 } else if ([node class] == [PithosSubdirNode class]) {
1472 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1473 dispatch_async(queue, ^{
1474 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1475 if (node.pithosObject.subdir)
1476 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1477 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1478 objectName:node.pithosObject.name
1479 destinationContainerName:containerName
1480 destinationObjectName:destinationObjectName
1482 if (objectRequests) {
1483 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1484 objectRequest.delegate = self;
1485 objectRequest.didFinishSelector = @selector(moveFinished:);
1486 objectRequest.didFailSelector = @selector(moveFailed:);
1487 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1488 node.parent, @"node",
1489 dropNode, @"dropNode",
1491 [objectRequest startAsynchronous];
1498 for (PithosNode *node in localClipboardNodes) {
1499 if (([node class] == [PithosObjectNode class]) ||
1500 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1501 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1502 dispatch_async(queue, ^{
1503 NSString *destinationObjectName;
1504 if (![dropNode isEqualTo:node.parent]) {
1505 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1506 if ([node.pithosObject.name hasSuffix:@"/"])
1507 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1509 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
1510 objectName:node.pithosObject.name];
1512 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1513 objectName:node.pithosObject.name
1514 destinationContainerName:containerName
1515 destinationObjectName:destinationObjectName
1517 if (objectRequest) {
1518 objectRequest.delegate = self;
1519 objectRequest.didFinishSelector = @selector(copyFinished:);
1520 objectRequest.didFailSelector = @selector(copyFailed:);
1521 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1522 dropNode, @"dropNode",
1524 [objectRequest startAsynchronous];
1527 } else if ([node class] == [PithosSubdirNode class]) {
1528 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1529 dispatch_async(queue, ^{
1530 NSString *destinationObjectName;
1531 if (![dropNode isEqualTo:node.parent]) {
1532 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1533 if (node.pithosObject.subdir)
1534 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1536 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
1537 subdirName:node.pithosObject.name];
1539 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1540 objectName:node.pithosObject.name
1541 destinationContainerName:containerName
1542 destinationObjectName:destinationObjectName
1544 if (objectRequests) {
1545 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1546 objectRequest.delegate = self;
1547 objectRequest.didFinishSelector = @selector(copyFinished:);
1548 objectRequest.didFailSelector = @selector(copyFailed:);
1549 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1550 dropNode, @"dropNode",
1552 [objectRequest startAsynchronous];
1562 #pragma mark Menu Actions ASIHTTPRequestDelegate
1564 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1565 if (objectRequest.responseStatusCode == 201) {
1566 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1567 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1573 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1577 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1578 NSLog(@"Creation of new application/directory object failed");
1579 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1582 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1583 if (objectRequest.responseStatusCode == 204) {
1584 NSLog(@"Object deleted: %@", [objectRequest url]);
1587 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1591 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1592 NSLog(@"Delete of object failed");
1593 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1596 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1597 if (objectRequest.responseStatusCode == 201) {
1598 NSLog(@"Object moved: %@", [objectRequest url]);
1601 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1605 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1606 NSLog(@"Move of object failed");
1607 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];