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;
99 - (void)getInfo:(NSMenuItem *)sender;
100 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest;
101 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest;
102 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
103 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
104 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest;
105 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest;
108 @implementation PithosBrowserController
109 @synthesize outlineViewDataSourceArray, splitView, outlineView, browser, draggedNodes, draggedParentNode;
112 #pragma Object Lifecycle
115 return [super initWithWindowNibName:@"PithosBrowserController"];
119 [[NSNotificationCenter defaultCenter] removeObserver:self];
120 [draggedParentNode release];
121 [draggedNodes release];
122 [browserMenu release];
123 [sharedPreviewController release];
124 [outlineViewDataSourceArray release];
125 [accountNode release];
130 - (void)awakeFromNib {
131 [super awakeFromNib];
133 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
134 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
135 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
137 [browser setCellClass:[PithosBrowserCell class]];
139 browserMenu = [[NSMenu alloc] init];
140 [browserMenu setDelegate:self];
141 [browser setMenu:browserMenu];
144 - (void)resetContainers:(NSNotification *)notification {
146 [browser loadColumnZero];
147 self.outlineViewDataSourceArray = nil;
149 // Create the outlineView tree
151 NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
152 [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
155 NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
156 [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
158 [[sharedTreeNode mutableChildNodes] addObject:
159 [NSTreeNode treeNodeWithRepresentedObject:
160 [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared"
161 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
163 // SHARED/others shared
164 [[sharedTreeNode mutableChildNodes] addObject:
165 [NSTreeNode treeNodeWithRepresentedObject:
166 [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
167 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
170 self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
172 // Expand the folder outline view
173 [outlineView expandItem:nil expandChildren:YES];
174 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
176 // Create accountNode and trigger a refresh
177 accountNode = [[PithosAccountNode alloc] init];
178 accountNode.children;
181 - (void)windowDidLoad {
182 [super windowDidLoad];
184 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
186 // Register for updates
187 [[NSNotificationCenter defaultCenter] addObserver:self
188 selector:@selector(pithosNodeChildrenUpdated:)
189 name:@"PithosContainerNodeChildrenUpdated"
191 [[NSNotificationCenter defaultCenter] addObserver:self
192 selector:@selector(pithosNodeChildrenUpdated:)
193 name:@"PithosSubdirNodeChildrenUpdated"
195 [[NSNotificationCenter defaultCenter] addObserver:self
196 selector:@selector(pithosAccountNodeChildrenUpdated:)
197 name:@"PithosAccountNodeChildrenUpdated"
199 [[NSNotificationCenter defaultCenter] addObserver:self
200 selector:@selector(resetContainers:)
201 name:@"PithosAuthenticationCredentialsUpdated"
208 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
209 PithosNode *node = (PithosNode *)[notification object];
210 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
211 NSInteger lastColumn = [browser lastColumn];
212 for (NSInteger column = lastColumn; column >= 0; column--) {
213 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
214 [browser reloadColumn:column];
215 // The following code is unnecessary since the pithosObject is set in the PithosNode in each refresh
216 // Furthermore it caused problems on delete, because a non-existing parent was asked for
217 //if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
218 // // This reloads the preview column
219 // [browser setLastColumn:column];
220 // [browser addColumn];
227 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
228 BOOL containerPithosFound = NO;
229 BOOL containerTrashFound = NO;
230 NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
231 for (PithosContainerNode *containerNode in accountNode.children) {
232 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
233 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
234 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
235 containerPithosFound = YES;
236 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
237 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
238 NSUInteger insertIndex = 1;
239 if (!containerPithosFound)
241 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
242 containerTrashFound = YES;
244 [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
247 BOOL refreshAccountNode = NO;
248 if (!containerPithosFound) {
250 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
251 [containerRequest startSynchronous];
252 if ([containerRequest error]) {
253 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
255 refreshAccountNode = YES;
258 if (!containerTrashFound) {
260 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
261 [containerRequest startSynchronous];
262 if ([containerRequest error]) {
263 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
265 refreshAccountNode = YES;
268 if (refreshAccountNode) {
269 [accountNode invalidateChildren];
270 accountNode.children;
272 [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
273 self.outlineViewDataSourceArray = outlineViewDataSourceArray;
275 // Expand the folder outline view
276 [outlineView expandItem:nil expandChildren:YES];
277 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
286 - (IBAction)refresh:(id)sender {
287 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
288 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
290 [browser validateVisibleColumns];
294 #pragma NSBrowserDelegate
296 - (id)rootItemForBrowser:(NSBrowser *)browser {
300 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
301 PithosNode *node = (PithosNode *)item;
302 return node.children.count;
305 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
306 PithosNode *node = (PithosNode *)item;
307 return [node.children objectAtIndex:index];
310 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
311 PithosNode *node = (PithosNode *)item;
312 return node.isLeafItem;
315 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
316 PithosNode *node = (PithosNode *)item;
320 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
321 if (sharedPreviewController == nil)
322 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
323 return sharedPreviewController;
326 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
327 // if (!forUserResize) {
328 // id item = [browser parentForItemsInColumn:columnIndex];
329 // if ([self browser:browser isLeafItem:item]) {
330 // suggestedWidth = 200;
333 // return suggestedWidth;
336 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
342 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
346 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
347 PithosNode *node = (PithosNode *)item;
348 NSString *newName = (NSString *)object;
349 NSUInteger newNameLength = [newName length];
350 NSRange firstSlashRange = [newName rangeOfString:@"/"];
351 if ((newNameLength == 0) ||
352 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
353 ([newName isEqualToString:node.displayName])) {
356 if (([node class] == [PithosObjectNode class]) ||
357 (([node class] == [PithosSubdirNode class]) &&
358 !node.pithosObject.subdir &&
359 [node.pithosObject.name hasSuffix:@"/"])) {
360 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
361 dispatch_async(queue, ^{
362 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
363 if ([newName hasSuffix:@"/"])
364 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
365 NSError *error = nil;
367 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
368 objectName:destinationObjectName
370 isDirectory:&isDirectory]) {
371 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
372 [alert setMessageText:@"Name Taken"];
373 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
374 [alert addButtonWithTitle:@"OK"];
380 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
381 objectName:node.pithosObject.name
382 destinationContainerName:node.pithosContainer.name
383 destinationObjectName:destinationObjectName
386 objectRequest.delegate = self;
387 objectRequest.didFinishSelector = @selector(moveFinished:);
388 objectRequest.didFailSelector = @selector(moveFailed:);
389 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
390 node.parent, @"node",
392 [objectRequest startAsynchronous];
395 } else if ([node class] == [PithosSubdirNode class]) {
396 if (firstSlashRange.length == 1)
398 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
399 dispatch_async(queue, ^{
400 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
401 NSError *error = nil;
403 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
404 objectName:destinationObjectName
406 isDirectory:&isDirectory]) {
407 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
408 [alert setMessageText:@"Name Taken"];
409 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
410 [alert addButtonWithTitle:@"OK"];
416 if (node.pithosObject.subdir)
417 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
418 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
419 objectName:node.pithosObject.name
420 destinationContainerName:node.pithosContainer.name
421 destinationObjectName:destinationObjectName
423 if (objectRequests) {
424 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
425 objectRequest.delegate = self;
426 objectRequest.didFinishSelector = @selector(moveFinished:);
427 objectRequest.didFailSelector = @selector(moveFailed:);
428 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
429 node.parent, @"node",
431 [objectRequest startAsynchronous];
438 #pragma mark Drag and Drop source
440 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
441 toPasteboard:(NSPasteboard *)pasteboard {
442 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
443 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
444 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
445 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
446 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
447 [propertyList addObject:[node.pithosObject.name pathExtension]];
448 [nodes addObject:node];
451 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
452 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
453 self.draggedNodes = nodes;
454 self.draggedParentNode = [browser parentForItemsInColumn:column];
458 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
459 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
460 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
461 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
462 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
463 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
465 // If the node is a subdir ask if the whole tree should be downloaded
466 if ([node class] == [PithosSubdirNode class]) {
467 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
468 [alert setMessageText:@"Download directory"];
469 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
470 [alert addButtonWithTitle:@"OK"];
471 [alert addButtonWithTitle:@"Cancel"];
472 NSInteger choice = [alert runModal];
473 if (choice == NSAlertFirstButtonReturn) {
474 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
475 dispatch_async(queue, ^{
476 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
477 objectName:node.pithosObject.name
478 toDirectory:[dropDestination path]
480 if (objectRequests) {
481 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
482 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
483 objectRequest.delegate = self;
484 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
485 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
486 [objectRequest startAsynchronous];
492 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
493 objectName:node.pithosObject.name
494 toDirectory:[dropDestination path]
497 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
498 objectRequest.delegate = self;
499 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
500 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
501 [objectRequest startAsynchronous];
508 #pragma mark Drag and Drop destination
510 - (NSDragOperation)browser:aBrowser
511 validateDrop:(id<NSDraggingInfo>)info
512 proposedRow:(NSInteger *)row
513 column:(NSInteger *)column
514 dropOperation:(NSBrowserDropOperation *)dropOperation {
515 NSDragOperation result = NSDragOperationNone;
516 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
517 // For a drop above, the drop is redirected to the parent item
518 if (*dropOperation == NSBrowserDropAbove)
520 // Only allow dropping in folders
523 // Check if the node is not a folder and if so redirect to the parent item
524 if ([[browser itemAtRow:*row inColumn:*column] class] != [PithosSubdirNode class])
527 *dropOperation = NSBrowserDropOn;
528 result = NSDragOperationCopy;
530 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
531 // For a drop above, the drop is redirected to the parent item
532 if (*dropOperation == NSBrowserDropAbove)
534 // Only allow dropping in folders
536 PithosNode *dropNode;
538 // Check if the node is not a folder and if so redirect to the parent item
539 dropNode = [browser itemAtRow:*row inColumn:*column];
540 if ([dropNode class] != [PithosSubdirNode class])
544 dropNode = [browser parentForItemsInColumn:*column];
547 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
548 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
549 if ((([dropNode class] == [PithosContainerNode class]) ||
550 dropNode.pithosObject.subdir ||
551 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
552 ![dropNode isEqualTo:draggedParentNode]) {
553 // ![dropNode isEqualTo:draggedParentNode] &&
554 // ![draggedNodes containsObject:dropNode]) {
555 result = NSDragOperationMove;
557 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
558 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
559 if (([dropNode class] == [PithosContainerNode class]) ||
560 dropNode.pithosObject.subdir ||
561 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
562 result = NSDragOperationCopy;
570 - (BOOL)browser:(NSBrowser *)aBrowser
571 acceptDrop:(id<NSDraggingInfo>)info
573 column:(NSInteger)column
574 dropOperation:(NSBrowserDropOperation)dropOperation {
575 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
576 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
577 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
578 if ((column != -1) && (filenames != nil)) {
579 PithosNode *node = nil;
581 node = [browser itemAtRow:row inColumn:column];
583 node = [browser parentForItemsInColumn:column];
584 NSLog(@"drag in node: %@", node.url);
585 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
588 NSFileManager *defaultManager = [NSFileManager defaultManager];
589 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
590 NSString *objectNamePrefix;
591 if ([node class] == [PithosSubdirNode class])
592 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
594 objectNamePrefix = [NSString string];
595 NSUInteger blockSize = node.pithosContainer.blockSize;
596 NSString *blockHash = node.pithosContainer.blockHash;
598 for (NSString *filePath in filenames) {
600 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
603 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
604 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
605 dispatch_async(queue, ^{
606 NSError *error = nil;
607 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
608 if (contentType == nil)
609 contentType = @"application/octet-stream";
611 NSLog(@"contentType detection error: %@", error);
612 NSArray *hashes = nil;
613 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
614 objectName:objectName
615 contentType:contentType
622 objectRequest.delegate = self;
623 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
624 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
625 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
626 containerName, @"containerName",
627 objectName, @"objectName",
628 contentType, @"contentType",
629 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
630 blockHash, @"blockHash",
631 filePath, @"filePath",
634 [NSNumber numberWithUnsignedInteger:10], @"iteration",
636 [objectRequest startAsynchronous];
640 // Upload directory, confirm first
641 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
642 [alert setMessageText:@"Upload directory"];
643 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
644 [alert addButtonWithTitle:@"OK"];
645 [alert addButtonWithTitle:@"Cancel"];
646 NSInteger choice = [alert runModal];
647 if (choice == NSAlertFirstButtonReturn) {
648 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
649 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
650 dispatch_async(queue, ^{
651 NSMutableArray *objectNames = nil;
652 NSMutableArray *contentTypes = nil;
653 NSMutableArray *filePaths = nil;
654 NSMutableArray *hashesArrays = nil;
655 NSMutableArray *directoryObjectRequests = nil;
656 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
657 objectName:objectName
660 forDirectory:filePath
662 objectNames:&objectNames
663 contentTypes:&contentTypes
665 hashesArrays:&hashesArrays
666 directoryObjectRequests:&directoryObjectRequests];
667 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
668 objectRequest.delegate = self;
669 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
670 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
671 [objectRequest startAsynchronous];
673 if (objectRequests) {
674 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
675 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
676 objectRequest.delegate = self;
677 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
678 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
679 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
680 containerName, @"containerName",
681 [objectNames objectAtIndex:i], @"objectName",
682 [contentTypes objectAtIndex:i], @"contentType",
683 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
684 blockHash, @"blockHash",
685 [filePaths objectAtIndex:i], @"filePath",
686 [hashesArrays objectAtIndex:i], @"hashes",
687 [NSNumber numberWithUnsignedInteger:10], @"iteration",
689 [objectRequest startAsynchronous];
700 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
701 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
702 if ((column != -1) && (draggedNodes != nil)) {
703 PithosNode *dropNode = nil;
705 dropNode = [browser itemAtRow:row inColumn:column];
707 dropNode = [browser parentForItemsInColumn:column];
708 NSLog(@"drag local node: %@", dropNode.url);
709 if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
712 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
713 NSString *objectNamePrefix;
714 if ([dropNode class] == [PithosSubdirNode class])
715 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
717 objectNamePrefix = [NSString string];
719 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
720 for (PithosNode *node in draggedNodes) {
721 if (([node class] == [PithosObjectNode class]) ||
722 (([node class] == [PithosSubdirNode class]) &&
723 !node.pithosObject.subdir &&
724 [node.pithosObject.name hasSuffix:@"/"])) {
725 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
726 dispatch_async(queue, ^{
727 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
728 if ([node.pithosObject.name hasSuffix:@"/"])
729 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
730 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
731 objectName:node.pithosObject.name
732 destinationContainerName:containerName
733 destinationObjectName:destinationObjectName
736 objectRequest.delegate = self;
737 objectRequest.didFinishSelector = @selector(moveFinished:);
738 objectRequest.didFailSelector = @selector(moveFailed:);
739 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
740 node.parent, @"node",
741 dropNode, @"dropNode",
743 [objectRequest startAsynchronous];
746 } else if ([node class] == [PithosSubdirNode class]) {
747 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
748 dispatch_async(queue, ^{
749 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
750 if (node.pithosObject.subdir)
751 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
752 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
753 objectName:node.pithosObject.name
754 destinationContainerName:containerName
755 destinationObjectName:destinationObjectName
757 if (objectRequests) {
758 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
759 objectRequest.delegate = self;
760 objectRequest.didFinishSelector = @selector(moveFinished:);
761 objectRequest.didFailSelector = @selector(moveFailed:);
762 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
763 node.parent, @"node",
764 dropNode, @"dropNode",
766 [objectRequest startAsynchronous];
772 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
773 for (PithosNode *node in draggedNodes) {
774 if (([node class] == [PithosObjectNode class]) ||
775 (([node class] == [PithosSubdirNode class]) &&
776 !node.pithosObject.subdir &&
777 [node.pithosObject.name hasSuffix:@"/"])) {
778 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
779 dispatch_async(queue, ^{
780 NSString *destinationObjectName;
781 if (![dropNode isEqualTo:node.parent]) {
782 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
783 if ([node.pithosObject.name hasSuffix:@"/"])
784 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
786 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
787 objectName:node.pithosObject.name];
789 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
790 objectName:node.pithosObject.name
791 destinationContainerName:containerName
792 destinationObjectName:destinationObjectName
795 objectRequest.delegate = self;
796 objectRequest.didFinishSelector = @selector(copyFinished:);
797 objectRequest.didFailSelector = @selector(copyFailed:);
798 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
799 dropNode, @"dropNode",
801 [objectRequest startAsynchronous];
804 } else if ([node class] == [PithosSubdirNode class]) {
805 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
806 dispatch_async(queue, ^{
807 NSString *destinationObjectName;
808 if (![dropNode isEqualTo:node.parent]) {
809 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
810 if (node.pithosObject.subdir)
811 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
813 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
814 subdirName:node.pithosObject.name];
816 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
817 objectName:node.pithosObject.name
818 destinationContainerName:containerName
819 destinationObjectName:destinationObjectName
821 if (objectRequests) {
822 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
823 objectRequest.delegate = self;
824 objectRequest.didFinishSelector = @selector(copyFinished:);
825 objectRequest.didFailSelector = @selector(copyFailed:);
826 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
827 dropNode, @"dropNode",
829 [objectRequest startAsynchronous];
844 #pragma mark ASIHTTPRequestDelegate
846 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
847 NSLog(@"Download completed: %@", [objectRequest url]);
848 if (objectRequest.responseStatusCode == 200) {
849 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
850 NSLog(@"Downloaded 0 bytes");
851 NSFileManager *defaultManager = [NSFileManager defaultManager];
852 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
853 if (![defaultManager fileExistsAtPath:filePath]) {
854 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
855 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
856 [alert setMessageText:@"Create File Error"];
857 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
858 [alert addButtonWithTitle:@"OK"];
864 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
868 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
869 NSLog(@"Download failed");
870 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
873 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
874 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
875 if (objectRequest.responseStatusCode == 201) {
876 NSLog(@"Directory object created: %@", [objectRequest url]);
879 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
883 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
884 NSLog(@"Upload directory object failed");
885 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
888 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
889 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
890 if (objectRequest.responseStatusCode == 201) {
891 NSLog(@"Object created: %@", [objectRequest url]);
892 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
894 [node invalidateChildren];
899 } else if (objectRequest.responseStatusCode == 409) {
900 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
901 if (iteration == 0) {
902 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
903 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
904 [alert setMessageText:@"Upload Timeout"];
905 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
906 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
907 [alert addButtonWithTitle:@"OK"];
911 NSLog(@"object is missing hashes: %@", [objectRequest url]);
912 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
913 withMissingHashesResponse:[objectRequest responseString]];
914 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
915 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
916 objectName:@".upload"
917 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
918 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
919 missingBlockIndex:missingBlockIndex];
920 newObjectRequest.delegate = self;
921 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
922 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
923 newObjectRequest.userInfo = objectRequest.userInfo;
924 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
925 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
926 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
927 [newObjectRequest startAsynchronous];
929 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
933 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
934 NSLog(@"Upload using hashmap failed");
935 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
938 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
939 NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
940 if (objectRequest.responseStatusCode == 201) {
941 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
942 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
943 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
944 if (missingBlockIndex == NSNotFound) {
945 NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
946 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
947 objectName:[objectRequest.userInfo objectForKey:@"objectName"]
948 contentType:[objectRequest.userInfo objectForKey:@"contentType"]
949 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
950 blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
951 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
954 newObjectRequest.delegate = self;
955 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
956 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
957 newObjectRequest.userInfo = objectRequest.userInfo;
958 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
959 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
960 [newObjectRequest startAsynchronous];
962 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
963 objectName:@".upload"
964 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
965 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
966 missingBlockIndex:missingBlockIndex];
967 newObjectRequest.delegate = self;
968 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
969 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
970 newObjectRequest.userInfo = objectRequest.userInfo;
971 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
972 [newObjectRequest startAsynchronous];
975 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
979 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
980 NSLog(@"Upload of missing block failed");
981 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
984 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
985 NSLog(@"Move object completed: %@", [objectRequest url]);
986 if (objectRequest.responseStatusCode == 201) {
987 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
988 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
990 [node invalidateChildren];
994 [dropNode invalidateChildren];
997 if (!node || !dropNode)
1000 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1004 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1005 NSLog(@"Move object failed");
1006 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1009 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1010 NSLog(@"Copy object completed: %@", [objectRequest url]);
1011 if (objectRequest.responseStatusCode == 201) {
1012 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1014 [dropNode invalidateChildren];
1020 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1024 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1025 NSLog(@"Copy object failed");
1026 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1030 #pragma mark NSSplitViewDelegate
1032 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1036 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1040 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1041 if (proposedPosition < 120)
1043 else if (proposedPosition > 220)
1046 return proposedPosition;
1050 #pragma mark NSOutlineViewDelegate
1052 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1053 return ([[item representedObject] isLeaf]);
1056 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1057 return (![[item representedObject] isLeaf]);
1060 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1061 PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
1064 [browser loadColumnZero];
1070 #pragma mark NSMenuDelegate
1072 - (void)menuNeedsUpdate:(NSMenu *)menu {
1073 NSInteger column = [browser clickedColumn];
1074 NSInteger row = [browser clickedRow];
1075 [menu removeAllItems];
1076 NSMenuItem *menuItem;
1077 if ((column == -1) || (row == -1)) {
1078 // General context menu
1079 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1080 PithosNode *menuNode;
1081 if ([menuNodesIndexPaths count] == 0) {
1082 menuNode = [browser parentForItemsInColumn:0];
1083 } else if (([menuNodesIndexPaths count] != 1) ||
1084 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1085 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1087 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1089 NSArray *menuNodes = [NSArray arrayWithObject:menuNode];
1091 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(newFolder:) keyEquivalent:@""] autorelease];
1092 [menuItem setRepresentedObject:menuNodes];
1093 [menu addItem:menuItem];
1094 [menu addItem:[NSMenuItem separatorItem]];
1096 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
1097 [menuItem setRepresentedObject:menuNodes];
1098 [menu addItem:menuItem];
1100 // Node context menu
1101 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1102 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1103 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1104 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1105 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1106 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1109 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1111 // Move to Trash (pithos container only)
1113 if ([rootNode class] == [PithosContainerNode class]) {
1114 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1115 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease];
1116 [menuItem setRepresentedObject:menuNodes];
1117 [menu addItem:menuItem];
1119 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
1120 [menuItem setRepresentedObject:menuNodes];
1121 [menu addItem:menuItem];
1122 [menu addItem:[NSMenuItem separatorItem]];
1125 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
1126 [menuItem setRepresentedObject:menuNodes];
1127 [menu addItem:menuItem];
1132 #pragma mark Menu Actions
1134 - (void)newFolder:(NSMenuItem *)sender {
1135 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1136 if ([node class] == [PithosContainerNode class]) {
1137 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1138 dispatch_async(queue, ^{
1139 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1140 subdirName:@"untitled folder"];
1141 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1142 objectName:safeObjectName
1144 contentType:@"application/directory"
1146 contentDisposition:nil
1149 isPublic:ASIPithosObjectRequestPublicIgnore
1151 data:[NSData data]];
1152 objectRequest.delegate = self;
1153 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1154 objectRequest.didFailSelector = @selector(newFolderFailed:);
1155 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1158 [objectRequest startAsynchronous];
1160 } else if (([node class] == [PithosSubdirNode class]) &&
1161 (node.pithosObject.subdir ||
1162 ![node.pithosObject.name hasSuffix:@"/"])) {
1163 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1164 dispatch_async(queue, ^{
1165 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1166 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1167 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1168 objectName:safeObjectName
1170 contentType:@"application/directory"
1172 contentDisposition:nil
1175 isPublic:ASIPithosObjectRequestPublicIgnore
1177 data:[NSData data]];
1178 objectRequest.delegate = self;
1179 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1180 objectRequest.didFailSelector = @selector(newFolderFailed:);
1181 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1184 [objectRequest startAsynchronous];
1190 - (void)getInfo:(NSMenuItem *)sender {
1191 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1192 [node showPithosNodeInfo:sender];
1196 - (void)deleteObject:(NSMenuItem *)sender {
1197 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1198 if (([node class] == [PithosObjectNode class]) ||
1199 (([node class] == [PithosSubdirNode class]) &&
1200 !node.pithosObject.subdir &&
1201 [node.pithosObject.name hasSuffix:@"/"])) {
1202 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1203 objectName:node.pithosObject.name];
1204 objectRequest.delegate = self;
1205 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1206 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1207 [objectRequest startAsynchronous];
1208 } else if ([node class] == [PithosSubdirNode class]) {
1209 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1210 dispatch_async(queue, ^{
1211 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1212 objectName:node.pithosObject.name];
1213 if (objectRequests) {
1214 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1215 objectRequest.delegate = self;
1216 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1217 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1218 [objectRequest startAsynchronous];
1226 - (void)moveToTrash:(NSMenuItem *)sender {
1227 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1228 if (([node class] == [PithosObjectNode class]) ||
1229 (([node class] == [PithosSubdirNode class]) &&
1230 !node.pithosObject.subdir &&
1231 [node.pithosObject.name hasSuffix:@"/"])) {
1232 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1233 dispatch_async(queue, ^{
1234 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1235 objectName:node.pithosObject.name];
1236 if (safeObjectName) {
1237 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1238 objectName:node.pithosObject.name
1239 destinationContainerName:@"trash"
1240 destinationObjectName:safeObjectName
1242 if (objectRequest) {
1243 objectRequest.delegate = self;
1244 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1245 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1246 [objectRequest startAsynchronous];
1250 } else if ([node class] == [PithosSubdirNode class]) {
1251 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1252 dispatch_async(queue, ^{
1253 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1254 subdirName:node.pithosObject.name];
1255 if (safeObjectName) {
1256 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1257 objectName:node.pithosObject.name
1258 destinationContainerName:@"trash"
1259 destinationObjectName:safeObjectName
1261 if (objectRequests) {
1262 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1263 objectRequest.delegate = self;
1264 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1265 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1266 [objectRequest startAsynchronous];
1276 #pragma mark Menu Actions ASIHTTPRequestDelegate
1278 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1279 if (objectRequest.responseStatusCode == 201) {
1280 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1281 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1283 [node invalidateChildren];
1289 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1293 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1294 NSLog(@"Creation of new application/directory object failed");
1295 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1298 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1299 if (objectRequest.responseStatusCode == 204) {
1300 NSLog(@"Object deleted: %@", [objectRequest url]);
1303 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1307 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1308 NSLog(@"Delete of object failed");
1309 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1312 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1313 if (objectRequest.responseStatusCode == 201) {
1314 NSLog(@"Object moved: %@", [objectRequest url]);
1317 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1321 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1322 NSLog(@"Move of object failed");
1323 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];