2 // PithosBrowserController.m
5 // Copyright 2011 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosSharingAccountsNode.h"
45 #import "PithosEmptyNode.h"
46 #import "ImageAndTextCell.h"
47 #import "FileSystemBrowserCell.h"
48 #import "ASIPithosRequest.h"
49 #import "ASIPithosContainerRequest.h"
50 #import "ASIPithosObjectRequest.h"
51 #import "ASIPithosContainer.h"
52 #import "ASIPithosObject.h"
53 #import "PithosFileUtilities.h"
55 @interface PithosBrowserCell : FileSystemBrowserCell {}
58 @implementation PithosBrowserCell
61 if ((self = [super init])) {
62 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
63 [self setEditable:YES];
68 - (void)setObjectValue:(id)object {
69 if ([object isKindOfClass:[PithosNode class]]) {
70 PithosNode *node = (PithosNode *)object;
71 [self setStringValue:node.displayName];
72 [self setImage:node.icon];
74 [super setObjectValue:object];
80 @interface PithosOutlineViewCell : ImageAndTextCell {}
83 @implementation PithosOutlineViewCell
85 - (void)setObjectValue:(id)object {
86 if ([object isKindOfClass:[PithosNode class]]) {
87 PithosNode *node = (PithosNode *)object;
88 [self setStringValue:node.displayName];
89 [self setImage:node.icon];
90 [self setEditable:NO];
92 [super setObjectValue:object];
98 @interface PithosBrowserController (Private) {}
99 - (void)resetContainers:(NSNotification *)notification;
102 @implementation PithosBrowserController
103 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser;
104 @synthesize draggedNodes, draggedParentNode;
105 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
108 #pragma Object Lifecycle
111 return [super initWithWindowNibName:@"PithosBrowserController"];
115 [[NSNotificationCenter defaultCenter] removeObserver:self];
116 [clipboardParentNode release];
117 [clipboardNodes release];
118 [draggedParentNode release];
119 [draggedNodes release];
120 [outlineViewMenu release];
121 [browserMenu release];
122 [sharedPreviewController release];
123 [othersSharedNode release];
124 [mySharedNode release];
125 [sharedNode release];
126 [containersNodeChildren release];
127 [containersNode release];
128 [accountNode release];
133 - (void)awakeFromNib {
134 [super awakeFromNib];
136 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
137 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
138 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
140 [browser setCellClass:[PithosBrowserCell class]];
142 browserMenu = [[NSMenu alloc] init];
143 [browserMenu setDelegate:self];
144 [browser setMenu:browserMenu];
146 outlineViewMenu = [[NSMenu alloc] init];
147 [outlineViewMenu setDelegate:self];
148 [outlineView setMenu:outlineViewMenu];
151 - (void)resetContainers:(NSNotification *)notification {
153 [browser loadColumnZero];
154 [containersNodeChildren removeAllObjects];
155 [outlineView reloadData];
156 // Expand the folder outline view
157 [outlineView expandItem:nil expandChildren:YES];
158 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
161 [accountNode refresh];
162 [mySharedNode refresh];
163 [othersSharedNode refresh];
166 - (void)windowDidLoad {
167 [super windowDidLoad];
169 accountNode = [[PithosAccountNode alloc] init];
170 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
171 containersNodeChildren = [[NSMutableArray alloc] init];
172 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
173 mySharedNode = [[PithosAccountNode alloc] init];
174 mySharedNode.displayName = @"my shared";
175 mySharedNode.shared = YES;
176 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
177 othersSharedNode = [[PithosSharingAccountsNode alloc] init];
178 othersSharedNode.displayName = @"others shared";
179 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
181 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
183 // Register for updates
184 // PithosContainerNode updates browser nodes
185 [[NSNotificationCenter defaultCenter] addObserver:self
186 selector:@selector(pithosNodeChildrenUpdated:)
187 name:@"PithosContainerNodeChildrenUpdated"
189 // PithosSubdirNode updates browser nodes
190 [[NSNotificationCenter defaultCenter] addObserver:self
191 selector:@selector(pithosNodeChildrenUpdated:)
192 name:@"PithosSubdirNodeChildrenUpdated"
194 // PithosAccountNode accountNode updates outlineView container nodes
195 [[NSNotificationCenter defaultCenter] addObserver:self
196 selector:@selector(pithosAccountNodeChildrenUpdated:)
197 name:@"PithosAccountNodeChildrenUpdated"
199 // PithosAccountNode other than accountNode updates nodes
200 [[NSNotificationCenter defaultCenter] addObserver:self
201 selector:@selector(pithosNodeChildrenUpdated:)
202 name:@"PithosAccountNodeChildrenUpdated"
204 // PithosSharingAccountsNode othersSharedNode updates browser nodes
205 [[NSNotificationCenter defaultCenter] addObserver:self
206 selector:@selector(pithosNodeChildrenUpdated:)
207 name:@"PithosSharingAccountsNodeChildrenUpdated"
208 object:othersSharedNode];
209 // Updated authentication credentials reset containers in the outline view
210 [[NSNotificationCenter defaultCenter] addObserver:self
211 selector:@selector(resetContainers:)
212 name:@"PithosAuthenticationCredentialsUpdated"
214 // Request for browser refresh
215 [[NSNotificationCenter defaultCenter] addObserver:self
216 selector:@selector(pithosBrowserRefreshNeeded:)
217 name:@"PithosBrowserRefreshNeeeded"
222 #pragma mark Observers
224 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
225 PithosNode *node = (PithosNode *)[notification object];
226 if (node == accountNode)
228 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
229 NSInteger lastColumn = [browser lastColumn];
230 for (NSInteger column = lastColumn; column >= 0; column--) {
231 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
232 [browser reloadColumn:column];
238 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
239 BOOL containerPithosFound = NO;
240 BOOL containerTrashFound = NO;
241 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
242 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
243 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
244 [removedContainersNodeChildren addIndex:i];
246 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
247 for (PithosContainerNode *containerNode in accountNode.children) {
248 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
249 if (![containersNodeChildren containsObject:containerNode])
250 [containersNodeChildren insertObject:containerNode atIndex:0];
251 containerPithosFound = YES;
252 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
253 NSUInteger insertIndex = 1;
254 if (!containerPithosFound)
256 if (![containersNodeChildren containsObject:containerNode])
257 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
258 containerTrashFound = YES;
259 } else if (![containersNodeChildren containsObject:containerNode]) {
260 [containersNodeChildren addObject:containerNode];
263 BOOL refreshAccountNode = NO;
264 if (!containerPithosFound) {
265 // Create pithos node
266 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
267 [containerRequest startSynchronous];
268 if ([containerRequest error]) {
269 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
271 refreshAccountNode = YES;
274 if (!containerTrashFound) {
276 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
277 [containerRequest startSynchronous];
278 if ([containerRequest error]) {
279 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
281 refreshAccountNode = YES;
285 if (refreshAccountNode)
286 [accountNode refresh];
288 [outlineView reloadData];
290 // Expand the folder outline view
291 [outlineView expandItem:nil expandChildren:YES];
293 if ((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) {
294 rootNode = [containersNodeChildren objectAtIndex:0];
295 [browser loadColumnZero];
302 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
309 - (IBAction)refresh:(id)sender {
310 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
312 [accountNode forceRefresh];
313 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
314 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
315 node.forcedRefresh = YES;
316 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
317 //[(PithosNode *)[browser parentForItemsInColumn:column] forceRefresh];
321 [accountNode refresh];
322 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
323 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
326 [browser validateVisibleColumns];
330 #pragma mark NSBrowserDelegate
332 - (id)rootItemForBrowser:(NSBrowser *)browser {
336 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
337 PithosNode *node = (PithosNode *)item;
338 return node.children.count;
341 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
342 PithosNode *node = (PithosNode *)item;
343 return [node.children objectAtIndex:index];
346 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
347 PithosNode *node = (PithosNode *)item;
348 return node.isLeafItem;
351 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
352 PithosNode *node = (PithosNode *)item;
356 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
357 if (sharedPreviewController == nil)
358 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
359 return sharedPreviewController;
362 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
363 // if (!forUserResize) {
364 // id item = [browser parentForItemsInColumn:columnIndex];
365 // if ([self browser:browser isLeafItem:item]) {
366 // suggestedWidth = 200;
369 // return suggestedWidth;
372 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
378 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
379 PithosNode *node = (PithosNode *)item;
380 if (node.shared || node.sharingAccount ||
381 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
386 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
387 PithosNode *node = (PithosNode *)item;
388 NSString *newName = (NSString *)object;
389 NSUInteger newNameLength = [newName length];
390 NSRange firstSlashRange = [newName rangeOfString:@"/"];
391 if ((newNameLength == 0) ||
392 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
393 ([newName isEqualToString:node.displayName])) {
396 if (([node class] == [PithosObjectNode class]) ||
397 (([node class] == [PithosSubdirNode class]) &&
398 !node.pithosObject.subdir &&
399 [node.pithosObject.name hasSuffix:@"/"])) {
400 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
401 dispatch_async(queue, ^{
402 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
403 if ([newName hasSuffix:@"/"])
404 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
405 NSError *error = nil;
407 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
408 objectName:destinationObjectName
410 isDirectory:&isDirectory
411 sharingAccount:nil]) {
412 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
413 [alert setMessageText:@"Name Taken"];
414 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
415 [alert addButtonWithTitle:@"OK"];
421 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
422 objectName:node.pithosObject.name
423 destinationContainerName:node.pithosContainer.name
424 destinationObjectName:destinationObjectName
427 objectRequest.delegate = self;
428 objectRequest.didFinishSelector = @selector(moveFinished:);
429 objectRequest.didFailSelector = @selector(moveFailed:);
430 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
431 node.parent, @"node",
433 [objectRequest startAsynchronous];
436 } else if ([node class] == [PithosSubdirNode class]) {
437 if (firstSlashRange.length == 1)
439 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
440 dispatch_async(queue, ^{
441 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
442 NSError *error = nil;
444 if ([PithosFileUtilities objectExistsAtContainerName:node.pithosContainer.name
445 objectName:destinationObjectName
447 isDirectory:&isDirectory
448 sharingAccount:nil]) {
449 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
450 [alert setMessageText:@"Name Taken"];
451 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
452 [alert addButtonWithTitle:@"OK"];
458 if (node.pithosObject.subdir)
459 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
460 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
461 objectName:node.pithosObject.name
462 destinationContainerName:node.pithosContainer.name
463 destinationObjectName:destinationObjectName
465 if (objectRequests) {
466 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
467 objectRequest.delegate = self;
468 objectRequest.didFinishSelector = @selector(moveFinished:);
469 objectRequest.didFailSelector = @selector(moveFailed:);
470 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
471 node.parent, @"node",
473 [objectRequest startAsynchronous];
480 #pragma mark Drag and Drop source
482 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
483 withEvent:(NSEvent *)event {
484 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
485 __block BOOL result = YES;
486 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
487 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
488 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
496 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
497 toPasteboard:(NSPasteboard *)pasteboard {
498 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
499 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
500 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
501 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
502 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
503 [propertyList addObject:[node.pithosObject.name pathExtension]];
504 [nodes addObject:node];
507 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
508 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
509 self.draggedNodes = nodes;
510 self.draggedParentNode = [browser parentForItemsInColumn:column];
514 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
515 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
516 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
517 for (PithosNode *node in draggedNodes) {
518 // If the node is a subdir ask if the whole tree should be downloaded
519 if ([node class] == [PithosSubdirNode class]) {
520 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
521 [alert setMessageText:@"Download directory"];
522 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
523 [alert addButtonWithTitle:@"OK"];
524 [alert addButtonWithTitle:@"Cancel"];
525 NSInteger choice = [alert runModal];
526 if (choice == NSAlertFirstButtonReturn) {
527 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
528 dispatch_async(queue, ^{
529 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
530 objectName:node.pithosObject.name
531 toDirectory:[dropDestination path]
533 sharingAccount:node.sharingAccount];
534 if (objectRequests) {
535 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
536 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
537 objectRequest.delegate = self;
538 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
539 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
540 [objectRequest startAsynchronous];
546 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
547 objectName:node.pithosObject.name
548 toDirectory:[dropDestination path]
550 sharingAccount:node.sharingAccount];
552 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
553 objectRequest.delegate = self;
554 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
555 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
556 [objectRequest startAsynchronous];
563 #pragma mark Drag and Drop destination
565 - (NSDragOperation)browser:aBrowser
566 validateDrop:(id<NSDraggingInfo>)info
567 proposedRow:(NSInteger *)row
568 column:(NSInteger *)column
569 dropOperation:(NSBrowserDropOperation *)dropOperation {
570 NSDragOperation result = NSDragOperationNone;
571 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
572 // For a drop above, the drop is redirected to the parent item
573 if (*dropOperation == NSBrowserDropAbove)
575 // Only allow dropping in folders
577 PithosNode *dropNode;
579 // Check if the node is not a folder and if so redirect to the parent item
580 dropNode = [browser itemAtRow:*row inColumn:*column];
581 if ([dropNode class] == [PithosObjectNode class])
585 dropNode = [browser parentForItemsInColumn:*column];
587 if (!dropNode.shared &&
588 (!dropNode.sharingAccount ||
589 ([dropNode class] == [PithosSubdirNode class]) ||
590 ([dropNode class] == [PithosContainerNode class]))) {
591 *dropOperation = NSBrowserDropOn;
592 result = NSDragOperationCopy;
595 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
596 // For a drop above, the drop is redirected to the parent item
597 if (*dropOperation == NSBrowserDropAbove)
599 // Only allow dropping in folders
601 PithosNode *dropNode;
603 // Check if the node is not a folder and if so redirect to the parent item
604 dropNode = [browser itemAtRow:*row inColumn:*column];
605 if ([dropNode class] == [PithosObjectNode class])
609 dropNode = [browser parentForItemsInColumn:*column];
611 if (!dropNode.shared && !dropNode.sharingAccount) {
612 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
613 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
614 if ((([dropNode class] == [PithosContainerNode class]) ||
615 dropNode.pithosObject.subdir ||
616 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
617 ![dropNode isEqualTo:draggedParentNode]) {
618 // ![dropNode isEqualTo:draggedParentNode] &&
619 // ![draggedNodes containsObject:dropNode]) {
620 result = NSDragOperationMove;
622 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
623 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
624 if (([dropNode class] == [PithosContainerNode class]) ||
625 dropNode.pithosObject.subdir ||
626 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
627 result = NSDragOperationCopy;
636 - (BOOL)browser:(NSBrowser *)aBrowser
637 acceptDrop:(id<NSDraggingInfo>)info
639 column:(NSInteger)column
640 dropOperation:(NSBrowserDropOperation)dropOperation {
641 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
642 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
643 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
644 if ((column != -1) && (filenames != nil)) {
645 PithosNode *node = nil;
647 node = [browser itemAtRow:row inColumn:column];
649 node = [browser parentForItemsInColumn:column];
650 NSLog(@"drag in node: %@", node.url);
651 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
654 NSFileManager *defaultManager = [NSFileManager defaultManager];
655 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
656 NSString *objectNamePrefix;
657 if ([node class] == [PithosSubdirNode class])
658 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
660 objectNamePrefix = [NSString string];
661 NSUInteger blockSize = node.pithosContainer.blockSize;
662 NSString *blockHash = node.pithosContainer.blockHash;
664 for (NSString *filePath in filenames) {
666 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
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 NSError *error = nil;
673 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
674 if (contentType == nil)
675 contentType = @"application/octet-stream";
677 NSLog(@"contentType detection error: %@", error);
678 NSArray *hashes = nil;
679 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
680 objectName:objectName
681 contentType:contentType
687 sharingAccount:node.sharingAccount];
689 objectRequest.delegate = self;
690 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
691 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
692 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
693 containerName, @"containerName",
694 objectName, @"objectName",
695 contentType, @"contentType",
696 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
697 blockHash, @"blockHash",
698 filePath, @"filePath",
701 [NSNumber numberWithUnsignedInteger:10], @"iteration",
703 if (node.sharingAccount)
704 [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
705 objectRequest.userInfo = userInfo;
706 [objectRequest startAsynchronous];
710 // Upload directory, confirm first
711 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
712 [alert setMessageText:@"Upload directory"];
713 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
714 [alert addButtonWithTitle:@"OK"];
715 [alert addButtonWithTitle:@"Cancel"];
716 NSInteger choice = [alert runModal];
717 if (choice == NSAlertFirstButtonReturn) {
718 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
719 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
720 dispatch_async(queue, ^{
721 NSMutableArray *objectNames = nil;
722 NSMutableArray *contentTypes = nil;
723 NSMutableArray *filePaths = nil;
724 NSMutableArray *hashesArrays = nil;
725 NSMutableArray *directoryObjectRequests = nil;
726 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
727 objectName:objectName
730 forDirectory:filePath
732 objectNames:&objectNames
733 contentTypes:&contentTypes
735 hashesArrays:&hashesArrays
736 directoryObjectRequests:&directoryObjectRequests
737 sharingAccount:node.sharingAccount];
738 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
739 objectRequest.delegate = self;
740 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
741 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
742 [objectRequest startAsynchronous];
744 if (objectRequests) {
745 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
746 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
747 objectRequest.delegate = self;
748 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
749 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
750 NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
751 containerName, @"containerName",
752 [objectNames objectAtIndex:i], @"objectName",
753 [contentTypes objectAtIndex:i], @"contentType",
754 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
755 blockHash, @"blockHash",
756 [filePaths objectAtIndex:i], @"filePath",
757 [hashesArrays objectAtIndex:i], @"hashes",
758 [NSNumber numberWithUnsignedInteger:10], @"iteration",
760 if (node.sharingAccount)
761 [userInfo setObject:node.sharingAccount forKey:@"sharingAccount"];
762 objectRequest.userInfo = userInfo;
763 [objectRequest startAsynchronous];
774 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
775 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
776 if ((column != -1) && (draggedNodes != nil)) {
777 PithosNode *dropNode = nil;
779 dropNode = [browser itemAtRow:row inColumn:column];
781 dropNode = [browser parentForItemsInColumn:column];
782 NSLog(@"drag local node: %@", dropNode.url);
783 if (([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class]))
786 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
787 NSString *objectNamePrefix;
788 if ([dropNode class] == [PithosSubdirNode class])
789 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
791 objectNamePrefix = [NSString string];
793 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
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 = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
802 if ([node.pithosObject.name hasSuffix:@"/"])
803 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
804 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
805 objectName:node.pithosObject.name
806 destinationContainerName:containerName
807 destinationObjectName:destinationObjectName
810 objectRequest.delegate = self;
811 objectRequest.didFinishSelector = @selector(moveFinished:);
812 objectRequest.didFailSelector = @selector(moveFailed:);
813 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
814 node.parent, @"node",
815 dropNode, @"dropNode",
817 [objectRequest startAsynchronous];
820 } else if ([node class] == [PithosSubdirNode class]) {
821 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
822 dispatch_async(queue, ^{
823 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
824 if (node.pithosObject.subdir)
825 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
826 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
827 objectName:node.pithosObject.name
828 destinationContainerName:containerName
829 destinationObjectName:destinationObjectName
831 if (objectRequests) {
832 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
833 objectRequest.delegate = self;
834 objectRequest.didFinishSelector = @selector(moveFinished:);
835 objectRequest.didFailSelector = @selector(moveFailed:);
836 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
837 node.parent, @"node",
838 dropNode, @"dropNode",
840 [objectRequest startAsynchronous];
846 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
847 for (PithosNode *node in draggedNodes) {
848 if (([node class] == [PithosObjectNode class]) ||
849 (([node class] == [PithosSubdirNode class]) &&
850 !node.pithosObject.subdir &&
851 [node.pithosObject.name hasSuffix:@"/"])) {
852 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
853 dispatch_async(queue, ^{
854 NSString *destinationObjectName;
855 if (![dropNode isEqualTo:node.parent]) {
856 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
857 if ([node.pithosObject.name hasSuffix:@"/"])
858 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
860 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
861 objectName:node.pithosObject.name];
863 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
864 objectName:node.pithosObject.name
865 destinationContainerName:containerName
866 destinationObjectName:destinationObjectName
870 objectRequest.delegate = self;
871 objectRequest.didFinishSelector = @selector(copyFinished:);
872 objectRequest.didFailSelector = @selector(copyFailed:);
873 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
874 dropNode, @"dropNode",
876 [objectRequest startAsynchronous];
879 } else if ([node class] == [PithosSubdirNode class]) {
880 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
881 dispatch_async(queue, ^{
882 NSString *destinationObjectName;
883 if (![dropNode isEqualTo:node.parent]) {
884 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
885 if (node.pithosObject.subdir)
886 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
888 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
889 subdirName:node.pithosObject.name];
891 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
892 objectName:node.pithosObject.name
893 destinationContainerName:containerName
894 destinationObjectName:destinationObjectName
897 if (objectRequests) {
898 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
899 objectRequest.delegate = self;
900 objectRequest.didFinishSelector = @selector(copyFinished:);
901 objectRequest.didFailSelector = @selector(copyFailed:);
902 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
903 dropNode, @"dropNode",
905 [objectRequest startAsynchronous];
920 #pragma mark ASIHTTPRequestDelegate
922 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
923 NSLog(@"Download completed: %@", [objectRequest url]);
924 if (objectRequest.responseStatusCode == 200) {
925 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
926 NSLog(@"Downloaded 0 bytes");
927 NSFileManager *defaultManager = [NSFileManager defaultManager];
928 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
929 if (![defaultManager fileExistsAtPath:filePath]) {
930 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
931 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
932 [alert setMessageText:@"Create File Error"];
933 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
934 [alert addButtonWithTitle:@"OK"];
940 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
944 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
945 NSLog(@"Download failed");
946 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
949 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
950 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
951 if (objectRequest.responseStatusCode == 201) {
952 NSLog(@"Directory object created: %@", [objectRequest url]);
955 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
959 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
960 NSLog(@"Upload directory object failed");
961 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
964 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
965 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
966 if (objectRequest.responseStatusCode == 201) {
967 NSLog(@"Object created: %@", [objectRequest url]);
968 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
973 } else if (objectRequest.responseStatusCode == 409) {
974 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
975 if (iteration == 0) {
976 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
977 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
978 [alert setMessageText:@"Upload Timeout"];
979 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
980 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
981 [alert addButtonWithTitle:@"OK"];
985 NSLog(@"object is missing hashes: %@", [objectRequest url]);
986 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
987 withMissingHashesResponse:[objectRequest responseString]];
988 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
989 ASIPithosContainerRequest *newContainerRequest = [PithosFileUtilities updateContainerDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
990 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
991 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
992 missingBlockIndex:missingBlockIndex
993 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
994 newContainerRequest.delegate = self;
995 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
996 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
997 newContainerRequest.userInfo = objectRequest.userInfo;
998 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
999 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1000 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1001 [newContainerRequest startAsynchronous];
1003 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1007 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
1008 NSLog(@"Upload using hashmap failed");
1009 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1012 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1013 NSLog(@"Upload of missing block completed: %@", [containerRequest url]);
1014 if (containerRequest.responseStatusCode == 202) {
1015 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1016 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1017 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1018 if (missingBlockIndex == NSNotFound) {
1019 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1020 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1021 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1022 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1023 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1024 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1025 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1028 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1029 newObjectRequest.delegate = self;
1030 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1031 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1032 newObjectRequest.userInfo = containerRequest.userInfo;
1033 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1034 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1035 [newObjectRequest startAsynchronous];
1037 ASIPithosContainerRequest *newContainerRequest = [PithosFileUtilities updateContainerDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1038 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1039 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1040 missingBlockIndex:missingBlockIndex
1041 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1042 newContainerRequest.delegate = self;
1043 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1044 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1045 newContainerRequest.userInfo = containerRequest.userInfo;
1046 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1047 [newContainerRequest startAsynchronous];
1050 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1054 - (void)uploadMissingBlockFailed:(ASIPithosContainerRequest *)containerRequest {
1055 NSLog(@"Upload of missing block failed");
1056 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
1059 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1060 NSLog(@"Move object completed: %@", [objectRequest url]);
1061 if (objectRequest.responseStatusCode == 201) {
1062 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1063 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1068 if (!node || !dropNode)
1071 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1075 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1076 NSLog(@"Move object failed");
1077 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1080 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1081 NSLog(@"Copy object completed: %@", [objectRequest url]);
1082 if (objectRequest.responseStatusCode == 201) {
1083 PithosNode *dropNode = [objectRequest.userInfo objectForKey:@"dropNode"];
1089 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1093 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1094 NSLog(@"Copy object failed");
1095 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1099 #pragma mark NSSplitViewDelegate
1101 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1102 if (splitView == verticalSplitView)
1105 return ([horizontalSplitView bounds].size.height - 87);
1108 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1109 if (splitView == verticalSplitView)
1112 return ([horizontalSplitView bounds].size.height - 87);
1115 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1116 if (splitView == verticalSplitView) {
1117 if (proposedPosition < 120)
1119 else if (proposedPosition > 220)
1122 return proposedPosition;
1124 return ([horizontalSplitView bounds].size.height - 87);
1129 #pragma mark NSOutlineViewDataSource
1131 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1134 if (item == containersNode)
1135 return containersNodeChildren.count;
1136 if (item == sharedNode)
1141 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1143 return (!index ? containersNode : sharedNode);
1144 if (item == sharedNode)
1145 return (!index ? mySharedNode : othersSharedNode);
1146 return [containersNodeChildren objectAtIndex:index];
1149 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1150 if ((item == containersNode) || (item == sharedNode))
1155 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1156 PithosNode *node = (PithosNode *)item;
1161 #pragma mark NSOutlineViewDelegate
1163 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1164 if ((item == containersNode) || (item == sharedNode))
1169 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1170 if ((item == containersNode) || (item == sharedNode))
1175 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1176 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1179 [browser loadColumnZero];
1185 #pragma mark NSMenuDelegate
1187 - (void)menuNeedsUpdate:(NSMenu *)menu {
1188 [menu removeAllItems];
1189 NSMenuItem *menuItem;
1190 NSString *menuItemTitle;
1191 BOOL nodeContextMenu = NO;
1192 PithosNode *menuNode;
1193 NSMutableArray *menuNodes;
1194 if (menu == browserMenu) {
1195 NSInteger column = [browser clickedColumn];
1196 NSInteger row = [browser clickedRow];
1197 if ((column == -1) || (row == -1)) {
1198 // General context menu
1199 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1200 if ([menuNodesIndexPaths count] == 0) {
1201 menuNode = [browser parentForItemsInColumn:0];
1202 } else if (([menuNodesIndexPaths count] != 1) ||
1203 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1204 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1206 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1209 // Node context menu
1210 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1211 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1212 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1213 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1214 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1215 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1218 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1220 nodeContextMenu = YES;
1222 } else if (menu == outlineViewMenu) {
1223 NSInteger row = [outlineView clickedRow];
1225 row = [outlineView selectedRow];
1228 menuNode = [outlineView itemAtRow:row];
1231 if (!nodeContextMenu) {
1232 // General context menu
1233 if (([menuNode class] == [PithosAccountNode class]) ||
1234 ([menuNode class] == [PithosSharingAccountsNode class]) ||
1235 ([menuNode class] == [PithosEmptyNode class]))
1237 BOOL shared = menuNode.shared;
1238 BOOL sharingAccount = (menuNode.sharingAccount != nil);
1240 if (!shared && !sharingAccount) {
1241 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1242 [menuItem setRepresentedObject:menuNode];
1243 [menu addItem:menuItem];
1244 [menu addItem:[NSMenuItem separatorItem]];
1247 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1248 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1249 [menu addItem:menuItem];
1251 if (!shared && !sharingAccount) {
1252 if (clipboardNodes) {
1253 NSUInteger clipboardNodesCount = [clipboardNodes count];
1254 if (clipboardNodesCount == 0) {
1255 self.clipboardNodes = nil;
1256 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1257 if (clipboardNodesCount == 1)
1258 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1260 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1261 [menu addItem:[NSMenuItem separatorItem]];
1262 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1263 [menuItem setRepresentedObject:menuNode];
1264 [menu addItem:menuItem];
1269 // Node context menu
1270 NSUInteger menuNodesCount = [menuNodes count];
1271 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1272 BOOL shared = firstMenuNode.shared;
1273 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1274 // Move to Trash (pithos container only)
1276 if (!shared && !sharingAccount) {
1277 if ([rootNode class] == [PithosContainerNode class]) {
1278 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1279 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1280 [menuItem setRepresentedObject:menuNodes];
1281 [menu addItem:menuItem];
1283 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1284 [menuItem setRepresentedObject:menuNodes];
1285 [menu addItem:menuItem];
1286 [menu addItem:[NSMenuItem separatorItem]];
1290 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1291 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1292 [menuItem setRepresentedObject:menuNodes];
1293 [menu addItem:menuItem];
1295 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1296 [menu addItem:[NSMenuItem separatorItem]];
1299 if (!shared && !sharingAccount) {
1300 if (menuNodesCount == 1)
1301 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1303 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1304 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1305 [menuItem setRepresentedObject:menuNodes];
1306 [menu addItem:menuItem];
1309 if ((!shared && !sharingAccount) ||
1310 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1311 if (menuNodesCount == 1)
1312 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1314 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1315 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1316 [menuItem setRepresentedObject:menuNodes];
1317 [menu addItem:menuItem];
1320 if (!shared && !sharingAccount) {
1321 if (menuNodesCount == 1) {
1322 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1323 if (([menuNode class] == [PithosSubdirNode class]) &&
1324 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1325 if (clipboardNodes) {
1326 NSUInteger clipboardNodesCount = [clipboardNodes count];
1327 if (clipboardNodesCount == 0) {
1328 self.clipboardNodes = nil;
1329 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1330 if (clipboardNodesCount == 1)
1331 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1333 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1334 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1335 [menuItem setRepresentedObject:menuNode];
1336 [menu addItem:menuItem];
1346 #pragma mark Menu Actions
1348 - (void)menuNewFolder:(NSMenuItem *)sender {
1349 PithosNode *node = (PithosNode *)[sender representedObject];
1350 if ([node class] == [PithosContainerNode class]) {
1351 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1352 dispatch_async(queue, ^{
1353 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1354 subdirName:@"untitled folder"];
1355 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1356 objectName:safeObjectName
1358 contentType:@"application/directory"
1360 contentDisposition:nil
1363 isPublic:ASIPithosObjectRequestPublicIgnore
1365 data:[NSData data]];
1366 objectRequest.delegate = self;
1367 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1368 objectRequest.didFailSelector = @selector(newFolderFailed:);
1369 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1372 [objectRequest startAsynchronous];
1374 } else if (([node class] == [PithosSubdirNode class]) &&
1375 (node.pithosObject.subdir ||
1376 ![node.pithosObject.name hasSuffix:@"/"])) {
1377 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1378 dispatch_async(queue, ^{
1379 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1380 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1381 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1382 objectName:safeObjectName
1384 contentType:@"application/directory"
1386 contentDisposition:nil
1389 isPublic:ASIPithosObjectRequestPublicIgnore
1391 data:[NSData data]];
1392 objectRequest.delegate = self;
1393 objectRequest.didFinishSelector = @selector(newFolderFinished:);
1394 objectRequest.didFailSelector = @selector(newFolderFailed:);
1395 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1398 [objectRequest startAsynchronous];
1403 - (void)menuGetInfo:(NSMenuItem *)sender {
1404 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1405 [node showPithosNodeInfo:sender];
1409 - (void)menuDelete:(NSMenuItem *)sender {
1410 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1411 if (([node class] == [PithosObjectNode class]) ||
1412 (([node class] == [PithosSubdirNode class]) &&
1413 !node.pithosObject.subdir &&
1414 [node.pithosObject.name hasSuffix:@"/"])) {
1415 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1416 objectName:node.pithosObject.name];
1417 objectRequest.delegate = self;
1418 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1419 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1420 [objectRequest startAsynchronous];
1421 } else if ([node class] == [PithosSubdirNode class]) {
1422 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1423 dispatch_async(queue, ^{
1424 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1425 objectName:node.pithosObject.name];
1426 if (objectRequests) {
1427 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1428 objectRequest.delegate = self;
1429 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1430 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1431 [objectRequest startAsynchronous];
1439 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1440 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1441 if (([node class] == [PithosObjectNode class]) ||
1442 (([node class] == [PithosSubdirNode class]) &&
1443 !node.pithosObject.subdir &&
1444 [node.pithosObject.name hasSuffix:@"/"])) {
1445 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1446 dispatch_async(queue, ^{
1447 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
1448 objectName:node.pithosObject.name];
1449 if (safeObjectName) {
1450 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1451 objectName:node.pithosObject.name
1452 destinationContainerName:@"trash"
1453 destinationObjectName:safeObjectName
1455 if (objectRequest) {
1456 objectRequest.delegate = self;
1457 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1458 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1459 [objectRequest startAsynchronous];
1463 } else if ([node class] == [PithosSubdirNode class]) {
1464 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1465 dispatch_async(queue, ^{
1466 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
1467 subdirName:node.pithosObject.name];
1468 if (safeObjectName) {
1469 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1470 objectName:node.pithosObject.name
1471 destinationContainerName:@"trash"
1472 destinationObjectName:safeObjectName
1474 if (objectRequests) {
1475 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1476 objectRequest.delegate = self;
1477 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
1478 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
1479 [objectRequest startAsynchronous];
1488 - (void)menuCut:(NSMenuItem *)sender {
1489 self.clipboardNodes = (NSArray *)[sender representedObject];
1490 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1491 self.clipboardCopy = NO;
1494 - (void)menuCopy:(NSMenuItem *)sender {
1495 self.clipboardNodes = (NSArray *)[sender representedObject];
1496 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
1497 self.clipboardCopy = YES;
1500 - (void)menuPaste:(NSMenuItem *)sender {
1501 if (!clipboardNodes || ![clipboardNodes count])
1503 PithosNode *dropNode = (PithosNode *)[sender representedObject];
1504 if ((([dropNode class] != [PithosSubdirNode class]) && ([dropNode class] != [PithosContainerNode class])) ||
1505 (([dropNode class] == [PithosSubdirNode class]) && !dropNode.pithosObject.subdir && [dropNode.pithosObject.name hasSuffix:@"/"]))
1508 NSString *containerName = [NSString stringWithString:dropNode.pithosContainer.name];
1509 NSString *objectNamePrefix;
1510 if ([dropNode class] == [PithosSubdirNode class])
1511 objectNamePrefix = [NSString stringWithString:dropNode.pithosObject.name];
1513 objectNamePrefix = [NSString string];
1515 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
1516 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
1517 self.clipboardNodes = nil;
1518 self.clipboardParentNode = nil;
1519 for (PithosNode *node in localClipboardNodes) {
1520 if (([node class] == [PithosObjectNode class]) ||
1521 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1522 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1523 dispatch_async(queue, ^{
1524 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1525 if ([node.pithosObject.name hasSuffix:@"/"])
1526 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1527 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1528 objectName:node.pithosObject.name
1529 destinationContainerName:containerName
1530 destinationObjectName:destinationObjectName
1532 if (objectRequest) {
1533 objectRequest.delegate = self;
1534 objectRequest.didFinishSelector = @selector(moveFinished:);
1535 objectRequest.didFailSelector = @selector(moveFailed:);
1536 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1537 node.parent, @"node",
1538 dropNode, @"dropNode",
1540 [objectRequest startAsynchronous];
1543 } else if ([node class] == [PithosSubdirNode class]) {
1544 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1545 dispatch_async(queue, ^{
1546 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1547 if (node.pithosObject.subdir)
1548 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1549 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1550 objectName:node.pithosObject.name
1551 destinationContainerName:containerName
1552 destinationObjectName:destinationObjectName
1554 if (objectRequests) {
1555 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1556 objectRequest.delegate = self;
1557 objectRequest.didFinishSelector = @selector(moveFinished:);
1558 objectRequest.didFailSelector = @selector(moveFailed:);
1559 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1560 node.parent, @"node",
1561 dropNode, @"dropNode",
1563 [objectRequest startAsynchronous];
1570 for (PithosNode *node in localClipboardNodes) {
1571 if (([node class] == [PithosObjectNode class]) ||
1572 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1573 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1574 dispatch_async(queue, ^{
1575 NSString *destinationObjectName;
1576 if (![dropNode isEqualTo:node.parent]) {
1577 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1578 if ([node.pithosObject.name hasSuffix:@"/"])
1579 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1581 destinationObjectName = [PithosFileUtilities safeObjectNameForContainerName:containerName
1582 objectName:node.pithosObject.name];
1584 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1585 objectName:node.pithosObject.name
1586 destinationContainerName:containerName
1587 destinationObjectName:destinationObjectName
1589 sharingAccount:node.sharingAccount];
1590 if (objectRequest) {
1591 objectRequest.delegate = self;
1592 objectRequest.didFinishSelector = @selector(copyFinished:);
1593 objectRequest.didFailSelector = @selector(copyFailed:);
1594 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1595 dropNode, @"dropNode",
1597 [objectRequest startAsynchronous];
1600 } else if ([node class] == [PithosSubdirNode class]) {
1601 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1602 dispatch_async(queue, ^{
1603 NSString *destinationObjectName;
1604 if (![dropNode isEqualTo:node.parent]) {
1605 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1606 if (node.pithosObject.subdir)
1607 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1609 destinationObjectName = [PithosFileUtilities safeSubdirNameForContainerName:containerName
1610 subdirName:node.pithosObject.name];
1612 NSArray *objectRequests = [PithosFileUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1613 objectName:node.pithosObject.name
1614 destinationContainerName:containerName
1615 destinationObjectName:destinationObjectName
1617 sharingAccount:node.sharingAccount];
1618 if (objectRequests) {
1619 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1620 objectRequest.delegate = self;
1621 objectRequest.didFinishSelector = @selector(copyFinished:);
1622 objectRequest.didFailSelector = @selector(copyFailed:);
1623 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1624 dropNode, @"dropNode",
1626 [objectRequest startAsynchronous];
1636 #pragma mark Menu Actions ASIHTTPRequestDelegate
1638 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
1639 if (objectRequest.responseStatusCode == 201) {
1640 NSLog(@"New application/directory object created: %@", [objectRequest url]);
1641 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
1647 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1651 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
1652 NSLog(@"Creation of new application/directory object failed");
1653 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1656 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1657 if (objectRequest.responseStatusCode == 204) {
1658 NSLog(@"Object deleted: %@", [objectRequest url]);
1661 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1665 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1666 NSLog(@"Delete of object failed");
1667 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
1670 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
1671 if (objectRequest.responseStatusCode == 201) {
1672 NSLog(@"Object moved: %@", [objectRequest url]);
1675 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1679 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
1680 NSLog(@"Move of object failed");
1681 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];