// PithosBrowserController.m
// pithos-macos
//
-// Copyright 2011 GRNET S.A. All rights reserved.
+// Copyright 2011-2012 GRNET S.A. All rights reserved.
//
// Redistribution and use in source and binary forms, with or
// without modification, are permitted provided that the following
#import "PithosContainerNode.h"
#import "PithosSubdirNode.h"
#import "PithosObjectNode.h"
+#import "PithosSharingAccountsNode.h"
#import "PithosEmptyNode.h"
#import "ImageAndTextCell.h"
#import "FileSystemBrowserCell.h"
+#import "ASINetworkQueue.h"
#import "ASIPithosRequest.h"
+#import "ASIPithos.h"
#import "ASIPithosContainerRequest.h"
#import "ASIPithosObjectRequest.h"
+#import "ASIPithosAccount.h"
#import "ASIPithosContainer.h"
#import "ASIPithosObject.h"
-#import "PithosFileUtilities.h"
+#import "PithosUtilities.h"
+#import "UsingSizeTransformer.h"
+
+#define REFRESH_TIMER_INTERVAL 5
-//@interface PithosBrowserCell : NSBrowserCell {}
@interface PithosBrowserCell : FileSystemBrowserCell {}
@end
- (id)init {
if ((self = [super init])) {
[self setLineBreakMode:NSLineBreakByTruncatingMiddle];
+ [self setEditable:YES];
}
return self;
}
PithosNode *node = (PithosNode *)object;
[self setStringValue:node.displayName];
[self setImage:node.icon];
-// // All cells are set as leafs because a branchingImage is already set!
-// // Maybe this cell is already inside an NSBrowserCell
-// [self setLeaf:YES];
} else {
[super setObjectValue:object];
}
@end
-@interface PithosBrowserController (Private) {}
-- (void)resetContainers;
-- (void)getInfo:(NSMenuItem *)sender;
-- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadMissingHashesFinished:(ASIPithosObjectRequest *)objectRequest;
-- (void)uploadMissingHashesFailed:(ASIPithosObjectRequest *)objectRequest;
+@interface PithosBrowserController (Private)
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
+- (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
@end
@implementation PithosBrowserController
-@synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
+@synthesize pithos;
+@synthesize pithosAccountManager, accountNode;
+@synthesize draggedNodes, draggedParentNode;
+@synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
#pragma mark -
#pragma Object Lifecycle
return [super initWithWindowNibName:@"PithosBrowserController"];
}
-- (void)dealloc {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- [browserMenu release];
- [sharedPreviewController release];
- [outlineViewDataSourceArray release];
- [accountNode release];
- [rootNode release];
- [super dealloc];
+- (void)windowDidLoad {
+ [super windowDidLoad];
+ if (browser && !browserInitialized) {
+ browserInitialized = YES;
+ [self initBrowser];
+ }
}
-- (void)awakeFromNib {
- [super awakeFromNib];
-
- [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
- [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
+- (void)initBrowser {
+ [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
+ [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
[browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
+ [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
+ [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
+ [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
+
[browser setCellClass:[PithosBrowserCell class]];
+ [browser setAllowsBranchSelection:YES];
+ [browser setAllowsMultipleSelection:YES];
+ [browser setAllowsEmptySelection:YES];
+ [browser setAllowsTypeSelect:YES];
+ [browser setDoubleAction:@selector(browserDoubleAction:)];
- browserMenu = [[NSMenu alloc] init];
- [browserMenu setDelegate:self];
- [browser setMenu:browserMenu];
-}
-
-- (void)resetContainers {
- rootNode = nil;
- [browser loadColumnZero];
- self.outlineViewDataSourceArray = nil;
+ moveNetworkQueue = [[ASINetworkQueue alloc] init];
+ moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
+// moveNetworkQueue.maxConcurrentOperationCount = 1;
+ copyNetworkQueue = [[ASINetworkQueue alloc] init];
+ copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
+// copyNetworkQueue.maxConcurrentOperationCount = 1;
+ deleteNetworkQueue = [[ASINetworkQueue alloc] init];
+ deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
+// deleteNetworkQueue.maxConcurrentOperationCount = 1;
+ uploadNetworkQueue = [[ASINetworkQueue alloc] init];
+ uploadNetworkQueue.showAccurateProgress = YES;
+ uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
+// uploadNetworkQueue.maxConcurrentOperationCount = 1;
+ downloadNetworkQueue = [[ASINetworkQueue alloc] init];
+ downloadNetworkQueue.showAccurateProgress = YES;
+ downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
+// downloadNetworkQueue.maxConcurrentOperationCount = 1;
- // Create the outlineView tree
- // CONTAINERS
- NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
- [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
-// // CONTAINERS/pithos
-// [[containersTreeNode mutableChildNodes] addObject:
-// [NSTreeNode treeNodeWithRepresentedObject:
-// [[[PithosContainerNode alloc] initWithContainerName:@"pithos"
-// icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)]
-// ] autorelease]]];
-// // CONTAINERS/trash
-// [[containersTreeNode mutableChildNodes] addObject:
-// [NSTreeNode treeNodeWithRepresentedObject:
-// [[[PithosContainerNode alloc] initWithContainerName:@"trash"
-// icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)]
-// ] autorelease]]];
- // SHARED
- NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
- [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
- // SHARED/my shared
- [[sharedTreeNode mutableChildNodes] addObject:
- [NSTreeNode treeNodeWithRepresentedObject:
- [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared"
- icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
- ] autorelease]]];
- // SHARED/others shared
- [[sharedTreeNode mutableChildNodes] addObject:
- [NSTreeNode treeNodeWithRepresentedObject:
- [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
- icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
- ] autorelease]]];
+ moveQueue = [[NSOperationQueue alloc] init];
+ [moveQueue setSuspended:YES];
+ moveQueue.name = @"gr.grnet.pithos.MoveQueue";
+// moveQueue.maxConcurrentOperationCount = 1;
+ copyQueue = [[NSOperationQueue alloc] init];
+ [copyQueue setSuspended:YES];
+ copyQueue.name = @"gr.grnet.pithos.CopyQueue";
+// copyQueue.maxConcurrentOperationCount = 1;
+ deleteQueue = [[NSOperationQueue alloc] init];
+ [deleteQueue setSuspended:YES];
+ deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
+// deleteQueue.maxConcurrentOperationCount = 1;
+ uploadQueue = [[NSOperationQueue alloc] init];
+ [uploadQueue setSuspended:YES];
+ uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
+// uploadQueue.maxConcurrentOperationCount = 1;
+ downloadQueue = [[NSOperationQueue alloc] init];
+ [downloadQueue setSuspended:YES];
+ downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
+// downloadQueue.maxConcurrentOperationCount = 1;
- self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
+ moveCallbackQueue = [[NSOperationQueue alloc] init];
+ [moveCallbackQueue setSuspended:YES];
+ moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
+// moveCallbackQueue.maxConcurrentOperationCount = 1;
+ copyCallbackQueue = [[NSOperationQueue alloc] init];
+ [copyCallbackQueue setSuspended:YES];
+ copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
+// copyCallbackQueue.maxConcurrentOperationCount = 1;
+ deleteCallbackQueue = [[NSOperationQueue alloc] init];
+ [deleteCallbackQueue setSuspended:YES];
+ deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
+// deleteCallbackQueue.maxConcurrentOperationCount = 1;
+ uploadCallbackQueue = [[NSOperationQueue alloc] init];
+ [uploadCallbackQueue setSuspended:YES];
+ uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
+// uploadCallbackQueue.maxConcurrentOperationCount = 1;
+ downloadCallbackQueue = [[NSOperationQueue alloc] init];
+ [downloadCallbackQueue setSuspended:YES];
+ downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
+// downloadCallbackQueue.maxConcurrentOperationCount = 1;
- // Expand the folder outline view
- [outlineView expandItem:nil expandChildren:YES];
- [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
+ [activityProgressIndicator setUsesThreadedAnimation:YES];
+ [activityProgressIndicator setMinValue:0.0];
+ [activityProgressIndicator setMaxValue:1.0];
+ activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
- // Create accountNode and trigger a refresh
- accountNode = [[PithosAccountNode alloc] init];
- accountNode.children;
-}
-
-- (void)windowDidLoad {
- [super windowDidLoad];
+ self.accountNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
+ containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
+ containersNodeChildren = [[NSMutableArray alloc] init];
+ sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
+ mySharedNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
+ mySharedNode.displayName = @"shared by me";
+ mySharedNode.shared = YES;
+ mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
+ othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
+ othersSharedNode.displayName = @"shared with me";
+ othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
- [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
+ [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[PithosOutlineViewCell alloc] init]];
// Register for updates
+ // PithosAccountNode accountNode updates outlineView container nodes
[[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(pithosNodeChildrenUpdated:)
- name:@"PithosContainerNodeChildrenUpdated"
- object:nil];
+ selector:@selector(pithosAccountNodeChildrenUpdated:)
+ name:@"PithosNodeChildrenUpdated"
+ object:accountNode];
+ // PithosNode updates browser nodes
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(pithosNodeChildrenUpdated:)
- name:@"PithosSubdirNodeChildrenUpdated"
+ name:@"PithosNodeChildrenUpdated"
object:nil];
+ // Request for browser refresh
[[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(pithosAccountNodeChildrenUpdated:)
- name:@"PithosAccountNodeChildrenUpdated"
- object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self
- selector:@selector(resetContainers)
- name:@"PithosAuthenticationCredentialsUpdated"
+ selector:@selector(pithosBrowserRefreshNeeded:)
+ name:@"PithosBrowserRefreshNeeded"
object:nil];
}
+- (void)resetBrowser {
+ @synchronized(self) {
+ if (!browserActive)
+ return;
+ }
+
+ [refreshTimer invalidate];
+
+ [moveNetworkQueue reset];
+ [copyNetworkQueue reset];
+ [deleteNetworkQueue reset];
+ [uploadNetworkQueue reset];
+ [downloadNetworkQueue reset];
+
+ [moveQueue cancelAllOperations];
+ [moveQueue setSuspended:YES];
+ [copyQueue cancelAllOperations];
+ [copyQueue setSuspended:YES];
+ [deleteQueue cancelAllOperations];
+ [deleteQueue setSuspended:YES];
+ [uploadQueue cancelAllOperations];
+ [uploadQueue setSuspended:YES];
+ [downloadQueue cancelAllOperations];
+ [downloadQueue setSuspended:YES];
+
+ [moveCallbackQueue cancelAllOperations];
+ [moveCallbackQueue setSuspended:YES];
+ [copyCallbackQueue cancelAllOperations];
+ [copyCallbackQueue setSuspended:YES];
+ [deleteCallbackQueue cancelAllOperations];
+ [deleteCallbackQueue setSuspended:YES];
+ [uploadCallbackQueue cancelAllOperations];
+ [uploadCallbackQueue setSuspended:YES];
+ [downloadCallbackQueue cancelAllOperations];
+ [downloadCallbackQueue setSuspended:YES];
+
+ [accountNode pithosNodeWillBeRemoved];
+ [mySharedNode pithosNodeWillBeRemoved];
+ [othersSharedNode pithosNodeWillBeRemoved];
+
+ rootNode = nil;
+ [browser loadColumnZero];
+ [containersNodeChildren removeAllObjects];
+ [outlineView reloadData];
+ // Expand the folder outline view
+ [outlineView expandItem:nil expandChildren:YES];
+ [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
+
+ activityFacility.delegate = nil;
+ [activityProgressIndicator setDoubleValue:1.0];
+ [activityProgressIndicator stopAnimation:self];
+
+ @synchronized(self) {
+ browserActive = NO;
+ }
+}
+
+- (void)startBrowser {
+ @synchronized(self) {
+ if (browserActive)
+ return;
+ }
+
+ // In the improbable case of leftover operations
+ [moveNetworkQueue reset];
+ [copyNetworkQueue reset];
+ [deleteNetworkQueue reset];
+ [uploadNetworkQueue reset];
+ [downloadNetworkQueue reset];
+ [moveQueue cancelAllOperations];
+ [copyQueue cancelAllOperations];
+ [deleteQueue cancelAllOperations];
+ [uploadQueue cancelAllOperations];
+ [downloadQueue cancelAllOperations];
+ [moveCallbackQueue cancelAllOperations];
+ [copyCallbackQueue cancelAllOperations];
+ [deleteCallbackQueue cancelAllOperations];
+ [uploadCallbackQueue cancelAllOperations];
+ [downloadCallbackQueue cancelAllOperations];
+
+ [moveNetworkQueue go];
+ [copyNetworkQueue go];
+ [deleteNetworkQueue go];
+ [uploadNetworkQueue go];
+ [downloadNetworkQueue go];
+ [moveQueue setSuspended:NO];
+ [copyQueue setSuspended:NO];
+ [deleteQueue setSuspended:NO];
+ [uploadQueue setSuspended:NO];
+ [downloadQueue setSuspended:NO];
+ [moveCallbackQueue setSuspended:NO];
+ [copyCallbackQueue setSuspended:NO];
+ [deleteCallbackQueue setSuspended:NO];
+ [uploadCallbackQueue setSuspended:NO];
+ [downloadCallbackQueue setSuspended:NO];
+
+ accountNode.pithos = pithos;
+ accountNode.pithosAccountManager = pithosAccountManager;
+ [accountNode forceRefresh];
+ mySharedNode.pithos = pithos;
+ mySharedNode.pithosAccountManager = pithosAccountManager;
+ [mySharedNode forceRefresh];
+ othersSharedNode.pithos = pithos;
+ othersSharedNode.pithosAccountManager = pithosAccountManager;
+ [othersSharedNode forceRefresh];
+
+// [activityFacility reset];
+ activityFacility.delegate = self;
+
+ refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
+ target:self
+ selector:@selector(forceRefresh:)
+ userInfo:self
+ repeats:YES];
+ @synchronized(self) {
+ browserActive = YES;
+ }
+}
+
+- (BOOL)operationsPending {
+ return ([moveNetworkQueue operationCount] ||
+ [copyNetworkQueue operationCount] ||
+ [deleteNetworkQueue operationCount] ||
+ [uploadNetworkQueue operationCount] ||
+ [downloadNetworkQueue operationCount] ||
+ [moveQueue operationCount] ||
+ [copyQueue operationCount] ||
+ [deleteQueue operationCount] ||
+ [uploadQueue operationCount] ||
+ [downloadQueue operationCount] ||
+ [moveCallbackQueue operationCount] ||
+ [copyCallbackQueue operationCount] ||
+ [deleteCallbackQueue operationCount] ||
+ [uploadCallbackQueue operationCount] ||
+ [downloadCallbackQueue operationCount]);
+}
+
+- (void)dealloc {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [self resetBrowser];
+}
+
+- (void)setPithos:(ASIPithos *)aPithos {
+ if (aPithos) {
+ if (![aPithos.authUser isEqualToString:pithos.authUser] ||
+ ![aPithos.authToken isEqualToString:pithos.authToken] ||
+ ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
+ ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
+ [self resetBrowser];
+ pithos = aPithos;
+ [self startBrowser];
+ } else {
+ [self startBrowser];
+ }
+ }
+}
+
+
#pragma mark -
-#pragma Observers
+#pragma mark Observers
- (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
+ if (![NSThread isMainThread]) {
+ [self performSelectorOnMainThread:@selector(pithosNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
+ return;
+ }
PithosNode *node = (PithosNode *)[notification object];
+ if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
+ return;
+ DLog(@"pithosNodeChildrenUpdated:%@", node.url);
NSInteger lastColumn = [browser lastColumn];
for (NSInteger column = lastColumn; column >= 0; column--) {
if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
[browser reloadColumn:column];
- if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
- // This reloads the preview column
- [browser setLastColumn:column];
- [browser addColumn];
- }
return;
}
}
}
- (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
+ if (![NSThread isMainThread]) {
+ [self performSelectorOnMainThread:@selector(pithosAccountNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
+ return;
+ }
BOOL containerPithosFound = NO;
BOOL containerTrashFound = NO;
- //NSMutableArray *containersTreeNodeChildren = [[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes];
- NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
+ NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
+ for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
+ if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
+ [removedContainersNodeChildren addIndex:i];
+ }
+ [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
for (PithosContainerNode *containerNode in accountNode.children) {
if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
- containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
- [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
+ if (![containersNodeChildren containsObject:containerNode])
+ [containersNodeChildren insertObject:containerNode atIndex:0];
containerPithosFound = YES;
} else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
- containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
NSUInteger insertIndex = 1;
if (!containerPithosFound)
insertIndex = 0;
- [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
+ if (![containersNodeChildren containsObject:containerNode])
+ [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
containerTrashFound = YES;
- } else {
- [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
+ } else if (![containersNodeChildren containsObject:containerNode]) {
+ [containersNodeChildren addObject:containerNode];
}
}
BOOL refreshAccountNode = NO;
if (!containerPithosFound) {
- // create pithos
- ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
- [containerRequest startSynchronous];
+ // Create pithos node
+ ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
+ containerName:@"pithos"];
+ [PithosUtilities startAndWaitForRequest:containerRequest];
if ([containerRequest error]) {
- NSLog(@"error:%@", [containerRequest error]);
- // XXX do something on error
+ [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
} else {
refreshAccountNode = YES;
}
}
if (!containerTrashFound) {
- // create trash
- ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
- [containerRequest startSynchronous];
+ // Create trash node
+ ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
+ containerName:@"trash"];
+ [PithosUtilities startAndWaitForRequest:containerRequest];
if ([containerRequest error]) {
- NSLog(@"error:%@", [containerRequest error]);
- // XXX do something on error
+ [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
} else {
refreshAccountNode = YES;
}
}
- if (refreshAccountNode) {
- [accountNode invalidateChildren];
- accountNode.children;
- } else {
- [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
- self.outlineViewDataSourceArray = outlineViewDataSourceArray;
-
- // Expand the folder outline view
- [outlineView expandItem:nil expandChildren:YES];
- [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
-
- [self refresh:nil];
+
+ if (refreshAccountNode)
+ [accountNode refresh];
+
+ [outlineView reloadData];
+
+ // Expand the folder outline view
+ [outlineView expandItem:nil expandChildren:YES];
+
+ if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
+ rootNode = [containersNodeChildren objectAtIndex:0];
+ [browser loadColumnZero];
}
+
+ if (notification)
+ [self refresh:nil];
+}
+
+- (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
+ [self refresh:nil];
}
#pragma mark -
-#pragma Actions
+#pragma mark Actions
-- (IBAction)refresh:(id)sender {
+- (IBAction)forceRefresh:(id)sender {
+ if (![NSThread isMainThread]) {
+ [self performSelectorOnMainThread:@selector(forceRefresh:) withObject:sender waitUntilDone:NO];
+ return;
+ }
+ if (editingItem)
+ return;
+ if (sender)
+ [accountNode forceRefresh];
for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
- [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
+ PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
+ node.forcedRefresh = YES;
+ [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
}
[browser validateVisibleColumns];
}
+- (IBAction)refresh:(id)sender {
+ if (![NSThread isMainThread]) {
+ [self performSelectorOnMainThread:@selector(refresh:) withObject:sender waitUntilDone:NO];
+ return;
+ }
+ if (editingItem)
+ return;
+ if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
+ [self forceRefresh:sender];
+ } else {
+ if (sender)
+ [accountNode refresh];
+ for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
+ [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
+ }
+ [browser validateVisibleColumns];
+ }
+}
+
#pragma mark -
-#pragma NSBrowserDelegate
+#pragma mark NSBrowserDelegate
- (id)rootItemForBrowser:(NSBrowser *)browser {
return rootNode;
return NO;
}
+#pragma mark Editing
+
+- (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
+ PithosNode *node = (PithosNode *)item;
+ if (node.shared || node.sharingAccount ||
+ ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
+ return NO;
+ editingItem = YES;
+ return YES;
+}
+
+- (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
+ editingItem = NO;
+ PithosNode *node = (PithosNode *)item;
+ NSString *newName = (NSString *)object;
+ NSUInteger newNameLength = [newName length];
+ NSRange firstSlashRange = [newName rangeOfString:@"/"];
+ if ((newNameLength == 0) ||
+ ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
+ ([newName isEqualToString:node.displayName])) {
+ return;
+ }
+ if (([node class] == [PithosObjectNode class]) ||
+ (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Rename (move) an object or subdir/ node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
+ if ([newName hasSuffix:@"/"])
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ NSError *error = nil;
+ BOOL isDirectory;
+ if ([PithosUtilities objectExistsAtPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:destinationObjectName
+ error:&error
+ isDirectory:&isDirectory
+ sharingAccount:nil]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Name Taken"];
+ [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ });
+ return;
+ } else if (error) {
+ return;
+ }
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:node.pithosContainer.name
+ destinationObjectName:destinationObjectName
+ checkIfExists:NO];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ } else if ([node class] == [PithosSubdirNode class]) {
+ if (firstSlashRange.length == 1)
+ return;
+ // Operation: Rename (move) a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
+ NSError *error = nil;
+ BOOL isDirectory;
+ if ([PithosUtilities objectExistsAtPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:destinationObjectName
+ error:&error
+ isDirectory:&isDirectory
+ sharingAccount:nil]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Name Taken"];
+ [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ });
+ return;
+ } else if (error) {
+ return;
+ }
+ if (operation.isCancelled)
+ return;
+ if (node.pithosObject.subdir)
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:node.pithosContainer.name
+ destinationObjectName:destinationObjectName
+ checkIfExists:NO];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ }
+}
+
#pragma mark Drag and Drop source
+- (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
+ withEvent:(NSEvent *)event {
+ NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
+ __block BOOL result = YES;
+ [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
+ PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
+ if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
+ result = NO;
+ *stop = YES;
+ }
+ }];
+ return result;
+}
+
- (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
toPasteboard:(NSPasteboard *)pasteboard {
NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
- NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
- for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
- PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
+ NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
+ NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
+ [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
+ PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
[propertyList addObject:[node.pithosObject.name pathExtension]];
- }
+ [nodes addObject:node];
+ }];
[pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
[pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
-
+ self.draggedNodes = nodes;
+ self.draggedParentNode = [browser parentForItemsInColumn:column];
return YES;
}
- (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
- NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
- NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
- for (NSUInteger i = [rowIndexes firstIndex]; i <= [rowIndexes lastIndex]; i = [rowIndexes indexGreaterThanIndex:i]) {
- PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:i]];
-
+ NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
+ for (PithosNode *node in draggedNodes) {
+ [names addObject:node.displayName];
// If the node is a subdir ask if the whole tree should be downloaded
- if (node.pithosObject.subdir) {
- NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+ if ([node class] == [PithosSubdirNode class]) {
+ NSAlert *alert = [[NSAlert alloc] init];
[alert setMessageText:@"Download directory"];
[alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
[alert addButtonWithTitle:@"OK"];
[alert addButtonWithTitle:@"Cancel"];
NSInteger choice = [alert runModal];
- if (choice == NSAlertFirstButtonReturn) {
- NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
- objectName:node.pithosObject.name
- toDirectory:[dropDestination path]
- checkIfExists:YES];
- if (objectRequests) {
- for (ASIPithosObjectRequest *objectRequest in objectRequests) {
- [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
- // XXX set delegates and queue
- [objectRequest setCompletionBlock:^{
- NSLog(@"dl completed: %@", [objectRequest url]);
- }];
- [objectRequest setFailedBlock:^{
- NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
- }];
- [objectRequest startAsynchronous];
- }
- }
- }
+ if (choice == NSAlertFirstButtonReturn)
+ [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
} else {
- ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
- objectName:node.pithosObject.name
- toDirectory:[dropDestination path]
- checkIfExists:YES];
- if (objectRequest) {
- [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
- // XXX set delegates and queue
- [objectRequest setCompletionBlock:^{
- NSLog(@"dl completed: %@", [objectRequest url]);
- }];
- [objectRequest setFailedBlock:^{
- NSLog(@"dl failed: %@, error: %@", [objectRequest url], [objectRequest error]);
- }];
- [objectRequest startAsynchronous];
- }
+ [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
}
}
return names;
column:(NSInteger *)column
dropOperation:(NSBrowserDropOperation *)dropOperation {
NSDragOperation result = NSDragOperationNone;
- // Files from the finder are accepted
- if ([[[info draggingPasteboard] types] indexOfObject:NSFilenamesPboardType] != -1) {
- // For a between drop, we let the user drop "on" the parent item
+ if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+ // For a drop above, the drop is redirected to the parent item
if (*dropOperation == NSBrowserDropAbove)
*row = -1;
// Only allow dropping in folders
if (*column != -1) {
+ PithosNode *dropNode;
if (*row != -1) {
- PithosNode *node = [browser itemAtRow:*row inColumn:*column];
- if ([node class] != [PithosSubdirNode class])
+ // Check if the node is not a folder and if so redirect to the parent item
+ dropNode = [browser itemAtRow:*row inColumn:*column];
+ if ([dropNode class] == [PithosObjectNode class])
+ *row = -1;
+ }
+ if (*row == -1)
+ dropNode = [browser parentForItemsInColumn:*column];
+
+ if (!dropNode.shared &&
+ (!dropNode.sharingAccount ||
+ ([dropNode class] == [PithosSubdirNode class]) ||
+ ([dropNode class] == [PithosContainerNode class]))) {
+ *dropOperation = NSBrowserDropOn;
+ result = NSDragOperationCopy;
+ }
+ }
+ } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+ // For a drop above, the drop is redirected to the parent item
+ if (*dropOperation == NSBrowserDropAbove)
+ *row = -1;
+ // Only allow dropping in folders
+ if (*column != -1) {
+ PithosNode *dropNode;
+ if (*row != -1) {
+ // Check if the node is not a folder and if so redirect to the parent item
+ dropNode = [browser itemAtRow:*row inColumn:*column];
+ if ([dropNode class] == [PithosObjectNode class])
*row = -1;
- *dropOperation = NSBrowserDropOn;
- result = NSDragOperationCopy;
+ }
+ if (*row == -1)
+ dropNode = [browser parentForItemsInColumn:*column];
+
+ if (!dropNode.shared && !dropNode.sharingAccount) {
+ if ([info draggingSourceOperationMask] & NSDragOperationMove) {
+ // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
+ if ((([dropNode class] == [PithosContainerNode class]) ||
+ dropNode.pithosObject.subdir ||
+ ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
+ ![dropNode isEqualTo:draggedParentNode]) {
+ // ![dropNode isEqualTo:draggedParentNode] &&
+ // ![draggedNodes containsObject:dropNode]) {
+ result = NSDragOperationMove;
+ }
+ } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
+ // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
+ if (([dropNode class] == [PithosContainerNode class]) ||
+ dropNode.pithosObject.subdir ||
+ ![dropNode.pithosObject.name hasSuffix:@"/"]) {
+ result = NSDragOperationCopy;
+ }
+ }
}
}
}
- // XXX else local file promises
return result;
}
atRow:(NSInteger)row
column:(NSInteger)column
dropOperation:(NSBrowserDropOperation)dropOperation {
- NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
- NSLog(@"drag in filenames: %@", filenames);
- PithosNode *node = nil;
- if ((column != -1) && (filenames != nil)) {
- if (row != -1)
- node = [browser itemAtRow:row inColumn:column];
- else
- node = [browser parentForItemsInColumn:column];
- NSLog(@"drag in node: %@", node.url);
- if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
+ if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+ NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
+ DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
+ if ((column != -1) && (filenames != nil)) {
+ PithosNode *node;
+ if (row != -1)
+ node = [browser itemAtRow:row inColumn:column];
+ else
+ node = [browser parentForItemsInColumn:column];
+ DLog(@"drag in node: %@", node.url);
+ return [self uploadFiles:filenames toNode:node];
+ }
+ } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+ DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+ if ((column != -1) && (draggedNodes != nil)) {
+ PithosNode *node;
+ if (row != -1)
+ node = [browser itemAtRow:row inColumn:column];
+ else
+ node = [browser parentForItemsInColumn:column];
+ DLog(@"drag local node: %@", node.url);
+ if ([info draggingSourceOperationMask] & NSDragOperationMove)
+ return [self moveNodes:draggedNodes toNode:node];
+ else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+ return [self cpyNodes:draggedNodes toNode:node];
+ }
+ }
+ return NO;
+}
+
+#pragma mark -
+#pragma mark NSBrowser Actions
+
+- (void)browserDoubleAction:(id)sender {
+ NSInteger column = [browser clickedColumn];
+ NSInteger row = [browser clickedRow];
+ if ((column == -1) || (row == -1))
+ return;
+ NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
+ NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+ NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
+ if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
+ for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
+ [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
+ }
+ } else {
+ [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
+ }
+ NSMenuItem *menuItem = [[NSMenuItem alloc] init];
+ menuItem.representedObject = menuNodes;
+ [self menuDownload:menuItem];
+}
+
+#pragma mark -
+#pragma mark Drag and Drop methods
+
+- (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
+ version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
+ if ([node class] == [PithosSubdirNode class]) {
+ // XXX newFilename and version are ignored in the case of a subdir node for now
+ // Operation: Download a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ toDirectory:dirPath
+ checkIfExists:checkIfExists
+ sharingAccount:node.sharingAccount];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
+ message:[messagePrefix stringByAppendingString:@" (0%)"]
+ totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
+ currentBytes:0];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ downloadNetworkQueue, @"networkQueue",
+ @"download", @"operationType",
+ nil]];
+ [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ }
+ }
+ }];
+ [downloadQueue addOperation:operation];
+ } else if ([node class] == [PithosObjectNode class]) {
+ // Operation: Download an object node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ version:version
+ toDirectory:dirPath
+ withNewFileName:newFileName
+ checkIfExists:checkIfExists
+ sharingAccount:node.sharingAccount];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
+ message:[messagePrefix stringByAppendingString:@" (0%)"]
+ totalBytes:node.pithosObject.bytes
+ currentBytes:0];
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity withMessage:activity.message];
+ });
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ downloadNetworkQueue, @"networkQueue",
+ @"download", @"operationType",
+ nil]];
+ [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ }
+ }];
+ [downloadQueue addOperation:operation];
+ }
+}
+
+- (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
+ if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
+ return NO;
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+ NSString *objectNamePrefix;
+ if ([destinationNode class] == [PithosSubdirNode class])
+ objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+ else
+ objectNamePrefix = [NSString string];
+ if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
+ ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
+ containerName:containerName];
+ [PithosUtilities startAndWaitForRequest:containerRequest];
+ if ([containerRequest error]) {
+ [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
return NO;
-
- NSFileManager *defaultManager = [NSFileManager defaultManager];
- NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
- NSString *objectNamePrefix;
- if ([node class] == [PithosSubdirNode class])
- objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
- else
- objectNamePrefix = [NSString stringWithString:@""];
- NSUInteger blockSize = node.pithosContainer.blockSize;
- NSString *blockHash = node.pithosContainer.blockHash;
-
- for (NSString *filePath in filenames) {
- BOOL isDirectory;
- if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
- if (!isDirectory) {
- // Upload file
- NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
- // XXX request
- dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- dispatch_async(queue, ^{
+ } else if (containerRequest.responseStatusCode != 204) {
+ [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
+ return NO;
+ }
+ destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
+ destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
+ }
+ NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
+ NSString *blockHash = destinationNode.pithosContainer.blockHash;
+
+ for (NSString *filePath in filenames) {
+ BOOL isDirectory;
+ if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+ if (!isDirectory) {
+ // Upload file
+ NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
+ precomposedStringWithCanonicalMapping];
+ // Operation: Upload a local file
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
NSError *error = nil;
- NSURLResponse *response = nil;
- [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath]
- cachePolicy:NSURLCacheStorageNotAllowed
- timeoutInterval:.1]
- returningResponse:&response
- error:&error];
- NSString *contentType = [response MIMEType];
+ NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
if (contentType == nil)
- contentType = @"application/binary";
+ contentType = @"application/octet-stream";
+ #if DEBUG_PITHOS
if (error)
- NSLog(@"contentType detection error: %@", error);
+ DLog(@"contentType detection error: %@", error);
+ #endif
NSArray *hashes = nil;
- ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
- objectName:objectName
- contentType:contentType
- blockSize:blockSize
- blockHash:blockHash
- forFile:filePath
- checkIfExists:YES
- hashes:&hashes];
- if (objectRequest) {
- // XXX set delegates and queue
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
+ containerName:containerName
+ objectName:objectName
+ contentType:contentType
+ blockSize:blockSize
+ blockHash:blockHash
+ forFile:filePath
+ checkIfExists:YES
+ hashes:&hashes
+ sharingAccount:destinationNode.sharingAccount];
+ if (!operation.isCancelled && objectRequest) {
objectRequest.delegate = self;
- objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
- objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
- objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
- containerName, @"containerName",
- objectName, @"objectName",
- contentType, @"contentType",
- [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
- blockHash, @"blockHash",
- filePath, @"filePath",
- hashes, @"hashes",
- [NSNumber numberWithBool:YES], @"checkIfExists",
- node, @"node",
- [NSNumber numberWithUnsignedInteger:0], @"iteration",
- nil];
- [objectRequest startAsynchronous];
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
+ message:[messagePrefix stringByAppendingString:@" (0%)"]
+ totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
+ currentBytes:0];
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ containerName, @"containerName",
+ objectName, @"objectName",
+ contentType, @"contentType",
+ [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
+ blockHash, @"blockHash",
+ filePath, @"filePath",
+ hashes, @"hashes",
+ [NSArray arrayWithObject:destinationNode], @"refreshNodes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ [NSNumber numberWithUnsignedInteger:10], @"iteration",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ uploadNetworkQueue, @"networkQueue",
+ @"upload", @"operationType",
+ nil]];
+ if (destinationNode.sharingAccount)
+ [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
}
- // XXX else show alert?
- });
- } else {
- // Upload directory, confirm first
- // XXX implement this
+ }
+ }];
+ [uploadQueue addOperation:operation];
+ } else {
+ // Upload directory, confirm first
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Upload directory"];
+ [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert addButtonWithTitle:@"Cancel"];
+ NSInteger choice = [alert runModal];
+ if (choice == NSAlertFirstButtonReturn) {
+ NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
+ precomposedStringWithCanonicalMapping];
+ // Operation: Upload a local directory and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSMutableArray *objectNames = nil;
+ NSMutableArray *contentTypes = nil;
+ NSMutableArray *filePaths = nil;
+ NSMutableArray *hashesArrays = nil;
+ NSMutableArray *directoryObjectRequests = nil;
+ NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
+ containerName:containerName
+ objectName:objectName
+ blockSize:blockSize
+ blockHash:blockHash
+ forDirectory:filePath
+ checkIfExists:YES
+ objectNames:&objectNames
+ contentTypes:&contentTypes
+ filePaths:&filePaths
+ hashesArrays:&hashesArrays
+ directoryObjectRequests:&directoryObjectRequests
+ sharingAccount:destinationNode.sharingAccount];
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
+ message:messagePrefix];
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ uploadNetworkQueue, @"networkQueue",
+ @"upload", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ if (!operation.isCancelled && objectRequests) {
+ for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
+ message:[messagePrefix stringByAppendingString:@" (0%)"]
+ totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
+ currentBytes:0];
+ [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ containerName, @"containerName",
+ [objectNames objectAtIndex:i], @"objectName",
+ [contentTypes objectAtIndex:i], @"contentType",
+ [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
+ blockHash, @"blockHash",
+ [filePaths objectAtIndex:i], @"filePath",
+ [hashesArrays objectAtIndex:i], @"hashes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ [NSNumber numberWithUnsignedInteger:10], @"iteration",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ uploadNetworkQueue, @"networkQueue",
+ @"upload", @"operationType",
+ nil]];
+ if (destinationNode.sharingAccount)
+ [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
+ }
+ }
+ }
+ }];
+ [uploadQueue addOperation:operation];
}
}
-
}
- return YES;
}
+ return YES;
+}
- return NO;
+- (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
+ if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+ (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
+ return NO;
+ NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+ NSString *objectNamePrefix;
+ if ([destinationNode class] == [PithosSubdirNode class])
+ objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+ else
+ objectNamePrefix = [NSString string];
+
+ for (PithosNode *node in nodes) {
+ if (([node class] == [PithosObjectNode class]) ||
+ (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Move an object or subdir/ node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if ([node.pithosObject.name hasSuffix:@"/"])
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ } else if ([node class] == [PithosSubdirNode class]) {
+ // Operation: Move a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if (node.pithosObject.subdir)
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ }
+ }
+ return YES;
+}
+
+- (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
+ if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
+ (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
+ return NO;
+ NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
+ NSString *objectNamePrefix;
+ if ([destinationNode class] == [PithosSubdirNode class])
+ objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
+ else
+ objectNamePrefix = [NSString string];
+
+ for (PithosNode *node in nodes) {
+ if (([node class] == [PithosObjectNode class]) ||
+ (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Copy an object or subdir/ node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName;
+ if (![destinationNode isEqualTo:node.parent]) {
+ destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if ([node.pithosObject.name hasSuffix:@"/"])
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ } else {
+ destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
+ containerName:containerName
+ objectName:node.pithosObject.name];
+ }
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES
+ sharingAccount:node.sharingAccount];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ copyNetworkQueue, @"networkQueue",
+ @"copy", @"operationType",
+ nil]];
+ [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }];
+ [copyQueue addOperation:operation];
+ } else if ([node class] == [PithosSubdirNode class]) {
+ // Operation: Copy a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *destinationObjectName;
+ if (![destinationNode isEqualTo:node.parent]) {
+ destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
+ if (node.pithosObject.subdir)
+ destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
+ } else {
+ destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
+ containerName:containerName
+ subdirName:node.pithosObject.name];
+ }
+ if (operation.isCancelled)
+ return;
+ NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:containerName
+ destinationObjectName:destinationObjectName
+ checkIfExists:YES
+ sharingAccount:node.sharingAccount];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ copyNetworkQueue, @"networkQueue",
+ @"copy", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }];
+ [copyQueue addOperation:operation];
+ }
+ }
+ return YES;
}
#pragma mark -
#pragma mark ASIHTTPRequestDelegate
-- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"upload using hashmap completed: %@", [objectRequest url]);
- if (objectRequest.responseStatusCode == 201) {
- NSLog(@"object created: %@", [objectRequest url]);
- PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
- [node invalidateChildren];
- node.children;
- } else if (objectRequest.responseStatusCode == 409) {
- NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] + 1;
- if (iteration > 10) {
- NSLog(@"upload iteration limit reached: %@", [objectRequest url]);
- // XXX show alert
- return;
+- (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
+ NSOperationQueue *callbackQueue;
+ NSString *operationType = [request.userInfo objectForKey:@"operationType"];
+ if ([operationType isEqualToString:@"move"])
+ callbackQueue = moveCallbackQueue;
+ else if ([operationType isEqualToString:@"copy"])
+ callbackQueue = copyCallbackQueue;
+ else if ([operationType isEqualToString:@"delete"])
+ callbackQueue = deleteCallbackQueue;
+ else if ([operationType isEqualToString:@"upload"])
+ callbackQueue = uploadCallbackQueue;
+ else if ([operationType isEqualToString:@"download"])
+ callbackQueue = downloadCallbackQueue;
+ else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ return;
+ }
+ // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+ NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
+ selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
+ object:request];
+ operation.completionBlock = ^{
+ @autoreleasepool {
+ if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ }
}
- NSLog(@"object is missing hashes: %@", [objectRequest url]);
- ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
- objectName:@".upload"
- contentType:[objectRequest.userInfo objectForKey:@"contentType"]
- blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
- forFile:[objectRequest.userInfo objectForKey:@"filePath"]
- hashes:[objectRequest.userInfo objectForKey:@"hashes"]
- missingHashesResponse:[objectRequest responseString]
- checkIfExists:[[objectRequest.userInfo objectForKey:@"checkIfExists"] boolValue]];
- newObjectRequest.shouldAttemptPersistentConnection = NO;
- newObjectRequest.delegate = self;
- newObjectRequest.didFinishSelector = @selector(uploadMissingHashesFinished:);
- newObjectRequest.didFailSelector = @selector(uploadMissingHashesFailed:);
- newObjectRequest.userInfo = objectRequest.userInfo;
- [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithBool:NO] forKey:@"checkIfExists"];
- [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
- [newObjectRequest startAsynchronous];
- } else {
- NSAlert *alert = [[[NSAlert alloc] init] autorelease];
- [alert setMessageText:@"Unexpected Response Status"];
- [alert setInformativeText:[NSString stringWithFormat:@"Unexpected response status %d - %@", objectRequest.responseStatusCode, objectRequest.responseStatusMessage]];
- [alert addButtonWithTitle:@"OK"];
- [alert runModal];
- }
-}
-
-- (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"upload failed: %@, error: %@", [objectRequest url], [objectRequest error]);
- NSAlert *alert = [[[NSAlert alloc] init] autorelease];
- [alert setMessageText:@"HTTP Request Error"];
- [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [objectRequest error]]];
- [alert addButtonWithTitle:@"OK"];
- [alert runModal];
-}
-
-- (void)uploadMissingHashesFinished:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"upload of missing hashes completed: %@", [objectRequest url]);
- if ((objectRequest.responseStatusCode == 201) || (objectRequest.responseStatusCode == 204)) {
- NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
- ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
- objectName:[objectRequest.userInfo objectForKey:@"objectName"]
- contentType:[objectRequest.userInfo objectForKey:@"contentType"]
- blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
- blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
- forFile:[objectRequest.userInfo objectForKey:@"filePath"]
- checkIfExists:NO
- hashes:&hashes];
- newObjectRequest.delegate = self;
- newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
- newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
- newObjectRequest.userInfo = objectRequest.userInfo;
- [newObjectRequest startAsynchronous];
+ };
+ [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+ [callbackQueue addOperation:operation];
+}
+
+- (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
+ if (request.isCancelled) {
+ // Request has been cancelled
+ // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
+ [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
+ withObject:request];
} else {
- NSAlert *alert = [[[NSAlert alloc] init] autorelease];
- [alert setMessageText:@"Unexpected Response Status"];
- [alert setInformativeText:[NSString stringWithFormat:@"Unexpected response status %d - %@", objectRequest.responseStatusCode, objectRequest.responseStatusMessage]];
- [alert addButtonWithTitle:@"OK"];
- [alert runModal];
+ NSOperationQueue *callbackQueue;
+ NSString *operationType = [request.userInfo objectForKey:@"operationType"];
+ if ([operationType isEqualToString:@"move"])
+ callbackQueue = moveCallbackQueue;
+ else if ([operationType isEqualToString:@"copy"])
+ callbackQueue = copyCallbackQueue;
+ else if ([operationType isEqualToString:@"delete"])
+ callbackQueue = deleteCallbackQueue;
+ else if ([operationType isEqualToString:@"upload"])
+ callbackQueue = uploadCallbackQueue;
+ else if ([operationType isEqualToString:@"download"])
+ callbackQueue = downloadCallbackQueue;
+ else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ return;
+ }
+ // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
+ NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
+ selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
+ object:request];
+ operation.completionBlock = ^{
+ @autoreleasepool {
+ if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ }
+ }
+ };
+ [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
+ [callbackQueue addOperation:operation];
}
}
-- (void)uploadMissingHashesFailed:(ASIPithosObjectRequest *)objectRequest {
- NSLog(@"upload of missing hashes failed: %@, error: %@", [objectRequest url], [objectRequest error]);
- NSAlert *alert = [[[NSAlert alloc] init] autorelease];
- [alert setMessageText:@"HTTP Request Error"];
- [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", [objectRequest error]]];
- [alert addButtonWithTitle:@"OK"];
- [alert runModal];
+- (void)requestFailed:(ASIPithosRequest *)request {
+ @autoreleasepool {
+ NSOperation *operation = [request.userInfo objectForKey:@"operation"];
+ DLog(@"Request failed: %@", request.url);
+ if (operation.isCancelled)
+ return;
+ if (request.isCancelled) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
+ });
+ return;
+ }
+ NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
+ if (retries > 0) {
+ ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
+ [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
+ [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
+ [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
+ withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
+ });
+ if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
+ [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
+ else
+ [PithosUtilities httpRequestErrorAlertWithRequest:request];
+ }
+ }
+}
+
+- (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Download finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 200) {
+ NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
+ PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+ NSUInteger totalBytes = activity.totalBytes;
+
+ // XXX change contentLength to objectContentLength if it is fixed in the server
+ if ([objectRequest contentLength] == 0) {
+ // The check above was:
+ // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
+ // I checked for directory content types in order not to create a file in place of a directory,
+ // but this callback method is not called in the case of a directory download.
+ // It maybe the case though, when downloading an old version of an object, is of a directory content type.
+ // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
+ DLog(@"Downloaded 0 bytes");
+ NSFileManager *fileManager = [NSFileManager defaultManager];
+ if (![fileManager fileExistsAtPath:filePath]) {
+ if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Create File Error"];
+ [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ });
+ }
+ }
+ }
+
+ NSUInteger currentBytes = [objectRequest objectContentLength];
+ if (currentBytes == 0)
+ currentBytes = totalBytes;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
+ totalBytes:totalBytes
+ currentBytes:currentBytes];
+ });
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Upload directory object finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ DLog(@"Directory object created: %@", objectRequest.url);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+ [node forceRefresh];
+ }
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+ [node refresh];
+ }
+ if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+ [self forceRefresh:self];
+ else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+ [self refresh:self];
+ });
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Upload using hashmap finished: %@", objectRequest.url);
+ NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
+ PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
+ NSUInteger totalBytes = activity.totalBytes;
+ NSUInteger currentBytes = activity.currentBytes;
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ DLog(@"Object created: %@", objectRequest.url);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
+ totalBytes:totalBytes
+ currentBytes:totalBytes];
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+ [node forceRefresh];
+ }
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+ [node refresh];
+ }
+ if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+ [self forceRefresh:self];
+ else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+ [self refresh:self];
+ });
+ } else if (objectRequest.responseStatusCode == 409) {
+ NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
+ if (iteration == 0) {
+ DLog(@"Upload iteration limit reached: %@", objectRequest.url);
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:activity
+ withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
+ NSAlert *alert = [[NSAlert alloc] init];
+ [alert setMessageText:@"Upload Timeout"];
+ [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
+ [objectRequest.userInfo objectForKey:@"objectName"]]];
+ [alert addButtonWithTitle:@"OK"];
+ [alert runModal];
+ });
+ return;
+ }
+ DLog(@"object is missing hashes: %@", objectRequest.url);
+ NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
+ withMissingHashes:[objectRequest hashes]];
+ NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+ if (totalBytes >= [missingBlocks count]*blockSize)
+ currentBytes = totalBytes - [missingBlocks count]*blockSize;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
+ totalBytes:totalBytes
+ currentBytes:currentBytes];
+ });
+ NSUInteger missingBlockIndex = [missingBlocks firstIndex];
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:[objectRequest.userInfo objectForKey:@"containerName"]
+ blockSize:blockSize
+ forFile:[objectRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
+ newContainerRequest.delegate = self;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = objectRequest.userInfo;
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
+ [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
+ @autoreleasepool {
+ NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Upload of missing block finished: %@", containerRequest.url);
+ NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
+ NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
+ PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
+ if (operation.isCancelled) {
+ [self requestFailed:containerRequest];
+ } else if (containerRequest.responseStatusCode == 202) {
+ NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
+ NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
+ missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
+ if (missingBlockIndex == NSNotFound) {
+ NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
+ ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
+ containerName:[containerRequest.userInfo objectForKey:@"containerName"]
+ objectName:[containerRequest.userInfo objectForKey:@"objectName"]
+ contentType:[containerRequest.userInfo objectForKey:@"contentType"]
+ blockSize:blockSize
+ blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
+ forFile:[containerRequest.userInfo objectForKey:@"filePath"]
+ checkIfExists:NO
+ hashes:&hashes
+ sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
+ newObjectRequest.delegate = self;
+ newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newObjectRequest.userInfo = containerRequest.userInfo;
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
+ [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ } else {
+ __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
+ containerName:[containerRequest.userInfo objectForKey:@"containerName"]
+ blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
+ forFile:[containerRequest.userInfo objectForKey:@"filePath"]
+ missingBlockIndex:missingBlockIndex
+ sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
+ newContainerRequest.delegate = self;
+ newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ newContainerRequest.userInfo = containerRequest.userInfo;
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
+ [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
+ [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
+ [activityFacility updateActivity:activity
+ withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
+ totalBytes:activity.totalBytes
+ currentBytes:(activity.currentBytes + size)];
+ }];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
+ }
+ } else {
+ [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:containerRequest];
+ }
+ }
+}
+
+- (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Move object finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+ [node forceRefresh];
+ }
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+ [node refresh];
+ }
+ if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+ [self forceRefresh:self];
+ else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+ [self refresh:self];
+ });
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Copy object finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 201) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+ [node forceRefresh];
+ }
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+ [node refresh];
+ }
+ if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+ [self forceRefresh:self];
+ else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+ [self refresh:self];
+ });
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
+}
+
+- (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
+ @autoreleasepool {
+ NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
+ DLog(@"Delete object finished: %@", objectRequest.url);
+ if (operation.isCancelled) {
+ [self requestFailed:objectRequest];
+ } else if (objectRequest.responseStatusCode == 204) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
+ withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
+ [node forceRefresh];
+ }
+ for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
+ [node refresh];
+ }
+ if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
+ [self forceRefresh:self];
+ else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
+ [self refresh:self];
+ });
+ } else {
+ [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
+ [self requestFailed:objectRequest];
+ }
+ }
}
#pragma mark -
#pragma mark NSSplitViewDelegate
- (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
- return 120;
+ if (splitView == verticalSplitView)
+ return 140;
+ else
+ return ([horizontalSplitView bounds].size.height - 142);
}
- (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
- return 220;
+ if (splitView == verticalSplitView)
+ return 220;
+ else
+ return ([horizontalSplitView bounds].size.height - 108);
+}
+
+- (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view {
+ if (((splitView == verticalSplitView) && (view == leftView)) ||
+ ((splitView == horizontalSplitView) && (view == leftBottomView))) {
+ return NO;
+ }
+ return YES;
+}
+
+#pragma mark -
+#pragma mark NSOutlineViewDataSource
+
+- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
+ if (!browserInitialized)
+ return 0;
+ if (item == nil)
+ return 2;
+ if (item == containersNode)
+ return containersNodeChildren.count;
+ if (item == sharedNode)
+ return 2;
+ return 0;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
+ if (!browserInitialized)
+ return nil;
+ if (item == nil)
+ return (!index ? containersNode : sharedNode);
+ if (item == sharedNode)
+ return (!index ? mySharedNode : othersSharedNode);
+ return [containersNodeChildren objectAtIndex:index];
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
+ if ((item == containersNode) || (item == sharedNode))
+ return YES;
+ return NO;
+}
+
+- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
+ PithosNode *node = (PithosNode *)item;
+ return node;
+}
+
+#pragma mark Drag and Drop destination
+
+- (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
+ validateDrop:(id<NSDraggingInfo>)info
+ proposedItem:(id)item
+ proposedChildIndex:(NSInteger)index {
+ NSDragOperation result = NSDragOperationNone;
+ if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
+ return result;
+ PithosNode *dropNode = (PithosNode *)item;
+ if ([dropNode class] != [PithosContainerNode class])
+ return result;
+ if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+ result = NSDragOperationCopy;
+ } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+ if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
+ ([info draggingSourceOperationMask] & NSDragOperationMove)) {
+ // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
+ if (![dropNode isEqualTo:draggedParentNode])
+ result = NSDragOperationMove;
+ } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
+ // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
+ result = NSDragOperationCopy;
+ }
+ }
+ return result;
+}
+
+- (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
+ if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
+ NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
+ DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
+ if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
+ PithosNode *node = (PithosNode *)item;
+ DLog(@"drag in node: %@", node.url);
+ return [self uploadFiles:filenames toNode:node];
+ }
+ } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
+ DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
+ if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
+ PithosNode *node = (PithosNode *)item;
+ DLog(@"drag local node: %@", node.url);
+ if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
+ ([info draggingSourceOperationMask] & NSDragOperationMove))
+ return [self moveNodes:draggedNodes toNode:node];
+ else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
+ return [self cpyNodes:draggedNodes toNode:node];
+ }
+ }
+ return NO;
}
#pragma mark -
#pragma mark NSOutlineViewDelegate
- (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
- return ([[item representedObject] isLeaf]);
+ if ((item == containersNode) || (item == sharedNode))
+ return NO;
+ return YES;
}
- (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
- return (![[item representedObject] isLeaf]);
+ if ((item == containersNode) || (item == sharedNode))
+ return YES;
+ return NO;
}
- (void)outlineViewSelectionDidChange:(NSNotification *)notification {
- PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
+ PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
if (node) {
rootNode = node;
[browser loadColumnZero];
+ [self refresh:nil];
}
}
#pragma mark NSMenuDelegate
- (void)menuNeedsUpdate:(NSMenu *)menu {
- NSInteger column = [browser clickedColumn];
- NSInteger row = [browser clickedRow];
[menu removeAllItems];
- if ((column == -1) || (row == -1)) {
- // General context menu has 0
- } else {
- // PithosNode menu has 1 items
+ NSMenuItem *menuItem;
+ NSString *menuItemTitle;
+ BOOL nodeContextMenu = NO;
+ PithosNode *menuNode = nil;
+ NSMutableArray *menuNodes;
+ if (menu == browserMenu) {
+ NSInteger column = [browser clickedColumn];
+ NSInteger row = [browser clickedRow];
+ if ((column == -1) || (row == -1)) {
+ if (column == -1) {
+ // General context menu
+ NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+ if ([menuNodesIndexPaths count] == 0) {
+ menuNode = [browser parentForItemsInColumn:0];
+ } else if (([menuNodesIndexPaths count] != 1) ||
+ ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
+ menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+ } else {
+ menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+ }
+ } else {
+ menuNode = [browser parentForItemsInColumn:column];
+ if ([menuNode class] == [PithosObjectNode class]) {
+ // Node context menu
+ menuNodes = [NSMutableArray arrayWithObject:menuNode];
+ nodeContextMenu = YES;
+ }
+ // else
+ // General context menu
+ }
+ } else {
+ // Node context menu
+ NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
+ NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+ menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
+ if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
+ for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
+ [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
+ }
+ } else {
+ [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
+ }
+ nodeContextMenu = YES;
+ }
+ } else if (menu == outlineViewMenu) {
+ NSInteger row = [outlineView clickedRow];
+ if (row == -1)
+ row = [outlineView selectedRow];
+ if (row == -1)
+ return;
+ menuNode = [outlineView itemAtRow:row];
+ }
+
+ if (!nodeContextMenu) {
+ // General context menu
+ if (([menuNode class] == [PithosAccountNode class]) ||
+ ([menuNode class] == [PithosSharingAccountsNode class]) ||
+ ([menuNode class] == [PithosEmptyNode class]))
+ return;
+ // New Folder
+ if (!menuNode.shared && !menuNode.sharingAccount) {
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNode];
+ [menu addItem:menuItem];
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
+ // Refresh
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
+ [menu addItem:menuItem];
+ [menu addItem:[NSMenuItem separatorItem]];
// Get Info
- NSMenuItem *menuItem = [[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""];
- [menuItem setRepresentedObject:[browser itemAtRow:row inColumn:column]];
+ menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
+ action:@selector(menuGetInfo:)
+ keyEquivalent:@""];
+ [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
[menu addItem:menuItem];
+ // Paste
+ if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
+ (([menuNode class] == [PithosContainerNode class]) ||
+ (([menuNode class] == [PithosSubdirNode class]) &&
+ (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
+ NSUInteger clipboardNodesCount = [clipboardNodes count];
+ if (clipboardNodesCount == 0) {
+ self.clipboardNodes = nil;
+ } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
+ if (clipboardNodesCount == 1)
+ menuItemTitle = @"Paste Item";
+ else
+ menuItemTitle = @"Paste Items";
+ [menu addItem:[NSMenuItem separatorItem]];
+ menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNode];
+ [menu addItem:menuItem];
+ }
+ }
+ } else {
+ // Node context menu
+ NSUInteger menuNodesCount = [menuNodes count];
+ PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
+ // Download
+ if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNodes];
+ [menu addItem:menuItem];
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
+ // Move to Trash (pithos container only)
+ // Delete
+ if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
+ if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash"
+ action:@selector(menuMoveToTrash:)
+ keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNodes];
+ [menu addItem:menuItem];
+ }
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNodes];
+ [menu addItem:menuItem];
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
+ // Refresh
+ menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
+ [menu addItem:menuItem];
+ // Get Info
+ if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
+ [menu addItem:[NSMenuItem separatorItem]];
+ menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
+ action:@selector(menuGetInfo:)
+ keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNodes];
+ [menu addItem:menuItem];
+
+ if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
+ [menu addItem:[NSMenuItem separatorItem]];
+ }
+ // Cut
+ if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
+ if (menuNodesCount == 1)
+ menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
+ else
+ menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
+ menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNodes];
+ [menu addItem:menuItem];
+ }
+ // Copy
+ if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
+ (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
+ if (menuNodesCount == 1)
+ menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
+ else
+ menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
+ menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""];
+ [menuItem setRepresentedObject:menuNodes];
+ [menu addItem:menuItem];
+ }
+ // Paste
+ if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
+ ([firstMenuNode class] == [PithosSubdirNode class]) &&
+ (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
+ NSUInteger clipboardNodesCount = [clipboardNodes count];
+ if (clipboardNodesCount == 0) {
+ self.clipboardNodes = nil;
+ } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
+ if (clipboardNodesCount == 1)
+ menuItemTitle = @"Paste Item";
+ else
+ menuItemTitle = @"Paste Items";
+ menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
+ [menuItem setRepresentedObject:firstMenuNode];
+ [menu addItem:menuItem];
+ }
+ }
+ }
+}
+
+#pragma mark -
+#pragma mark NSMenuValidation
+
+- (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
+ if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
+ NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+ if ([menuNodesIndexPaths count] == 0)
+ return NO;
+
+ PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+ if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
+ ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
+ (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
+ ((menuItem.action == @selector(delete:)) &&
+ (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
+ ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
+ return NO;
+
+ NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
+ for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
+ [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
+ }
+ menuItem.representedObject = menuNodes;
+ } else if (menuItem.action == @selector(paste:)) {
+ if (!clipboardNodes || ![clipboardNodes count])
+ return NO;
+
+ NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
+ PithosNode *menuNode;
+ if ([menuNodesIndexPaths count] == 0)
+ menuNode = [browser parentForItemsInColumn:0];
+ else if (([menuNodesIndexPaths count] != 1) ||
+ ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
+ menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
+ else
+ menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
+
+ if (menuNode.shared || menuNode.sharingAccount ||
+ (([menuNode class] != [PithosContainerNode class]) &&
+ (([menuNode class] != [PithosSubdirNode class]) ||
+ (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
+ (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
+ return NO;
+
+ menuItem.representedObject = menuNode;
}
+ return YES;
+}
+
+- (void)cut:(NSMenuItem *)sender {
+ [self menuCut:sender];
+}
+
+- (void)copy:(NSMenuItem *)sender {
+ [self menuCopy:sender];
+}
+
+- (void)paste:(NSMenuItem *)sender {
+ [self menuPaste:sender];
+}
+
+- (void)delete:(NSMenuItem *)sender {
+ if (sender.tag == 0)
+ [self menuMoveToTrash:sender];
+ else
+ [self menuDelete:sender];
}
#pragma mark -
#pragma mark Menu Actions
-- (void)getInfo:(NSMenuItem *)sender {
- [(PithosNode *)[sender representedObject] showPithosNodeInfo:sender];
+- (void)menuNewFolder:(NSMenuItem *)sender {
+ PithosNode *node = (PithosNode *)[sender representedObject];
+ if ([node class] == [PithosContainerNode class]) {
+ // Operation: Create (upload) a new root application/directory object
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
+ containerName:node.pithosContainer.name
+ subdirName:@"untitled folder"];
+ NSString *fileName = [safeObjectName lastPathComponent];
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:safeObjectName
+ eTag:nil
+ contentType:@"application/directory"
+ contentEncoding:nil
+ contentDisposition:nil
+ manifest:nil
+ sharing:nil
+ isPublic:ASIPithosObjectRequestPublicIgnore
+ metadata:nil
+ data:[NSData data]];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
+ message:messagePrefix];
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ fileName, @"fileName",
+ [NSArray arrayWithObject:node], @"refreshNodes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ uploadNetworkQueue, @"networkQueue",
+ @"upload", @"operationType",
+ nil];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }];
+ [uploadQueue addOperation:operation];
+ } else if (([node class] == [PithosSubdirNode class]) &&
+ (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Create (upload) a new aplication/directory object
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
+ containerName:node.pithosContainer.name
+ subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
+ NSString *fileName = [safeObjectName lastPathComponent];
+ if (operation.isCancelled)
+ return;
+ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:safeObjectName
+ eTag:nil
+ contentType:@"application/directory"
+ contentEncoding:nil
+ contentDisposition:nil
+ manifest:nil
+ sharing:nil
+ isPublic:ASIPithosObjectRequestPublicIgnore
+ metadata:nil
+ data:[NSData data]];
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
+ message:messagePrefix];
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ fileName, @"fileName",
+ [NSArray arrayWithObject:node], @"refreshNodes",
+ [NSNumber numberWithBool:YES], @"refresh",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ uploadNetworkQueue, @"networkQueue",
+ @"upload", @"operationType",
+ nil];
+ [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }];
+ [uploadQueue addOperation:operation];
+ }
+}
+
+- (void)menuGetInfo:(NSMenuItem *)sender {
+ for (PithosNode *node in ((NSArray *)[sender representedObject])) {
+ [node showPithosNodeInfo:sender];
+ }
+}
+
+- (void)menuDownload:(NSMenuItem *)sender {
+ NSArray *nodes = (NSArray *)[sender representedObject];
+ PithosNode *firstNode = [nodes objectAtIndex:0];
+ if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
+ NSSavePanel *save = [NSSavePanel savePanel];
+ save.nameFieldStringValue = firstNode.displayName;
+ NSInteger result = [save runModal];
+ if (result == NSOKButton) {
+ NSString *destinationPath = save.URL.path;
+ NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
+ NSString *newFileName = [destinationPath lastPathComponent];
+ if ([destinationPath hasSuffix:@"/"])
+ newFileName = [newFileName stringByAppendingString:@"/"];
+ if ([firstNode.displayName isEqualToString:newFileName])
+ newFileName = nil;
+ [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
+ }
+ } else {
+ NSOpenPanel *open = [NSOpenPanel openPanel];
+ open.canChooseFiles = NO;
+ open.canChooseDirectories = YES;
+ open.canCreateDirectories = YES;
+ NSInteger result = [open runModal];
+ if (result == NSOKButton) {
+ NSString *directoryPath = open.URL.path;
+ for (PithosNode *node in nodes) {
+ [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
+ }
+ }
+ }
+}
+
+- (void)menuDelete:(NSMenuItem *)sender {
+ for (PithosNode *node in ((NSArray *)[sender representedObject])) {
+ if (([node class] == [PithosObjectNode class]) ||
+ (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Delete an object or subdir/ node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *fileName = [node.pithosObject.name lastPathComponent];
+ if ([node.pithosObject.name hasSuffix:@"/"])
+ fileName = [fileName stringByAppendingString:@"/"];
+ ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name];
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
+ message:messagePrefix];
+ objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
+ fileName, @"fileName",
+ [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ deleteNetworkQueue, @"networkQueue",
+ @"delete", @"operationType",
+ nil];
+ [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }];
+ [deleteQueue addOperation:operation];
+ } else if ([node class] == [PithosSubdirNode class]) {
+ // Operation: Delete a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ deleteNetworkQueue, @"networkQueue",
+ @"delete", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }];
+ [deleteQueue addOperation:operation];
+ }
+ }
+}
+
+- (void)menuMoveToTrash:(NSMenuItem *)sender {
+ for (PithosNode *node in ((NSArray *)[sender representedObject])) {
+ if (([node class] == [PithosObjectNode class]) ||
+ (([node class] == [PithosSubdirNode class]) &&
+ !node.pithosObject.subdir &&
+ [node.pithosObject.name hasSuffix:@"/"])) {
+ // Operation: Move to trash an object or subdir/ node
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
+ containerName:@"trash"
+ objectName:node.pithosObject.name];
+ if (!operation.isCancelled && safeObjectName) {
+ ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:@"trash"
+ destinationObjectName:safeObjectName
+ checkIfExists:NO];
+ if (!operation.isCancelled && objectRequest) {
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ } else if ([node class] == [PithosSubdirNode class]) {
+ // Operation: Move to trash a subdir node and its descendants
+ // The resulting ASIPithosObjectRequests are chained through dependencies
+ __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
+ @autoreleasepool {
+ if (operation.isCancelled)
+ return;
+ NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
+ containerName:@"trash"
+ subdirName:node.pithosObject.name];
+ if (!operation.isCancelled && safeObjectName) {
+ NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
+ containerName:node.pithosContainer.name
+ objectName:node.pithosObject.name
+ destinationContainerName:@"trash"
+ destinationObjectName:safeObjectName
+ checkIfExists:NO];
+ if (!operation.isCancelled && objectRequests) {
+ ASIPithosObjectRequest *previousObjectRequest = nil;
+ for (ASIPithosObjectRequest *objectRequest in objectRequests) {
+ if (operation.isCancelled)
+ return;
+ objectRequest.delegate = self;
+ objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
+ objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
+ NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
+ [objectRequest.userInfo objectForKey:@"sourceContainerName"],
+ [objectRequest.userInfo objectForKey:@"sourceObjectName"],
+ [objectRequest.userInfo objectForKey:@"destinationContainerName"],
+ [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
+ PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
+ message:messagePrefix];
+ [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
+ [NSDictionary dictionaryWithObjectsAndKeys:
+ [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
+ activity, @"activity",
+ [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
+ [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
+ [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
+ [NSNumber numberWithUnsignedInteger:10], @"retries",
+ NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
+ NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
+ moveNetworkQueue, @"networkQueue",
+ @"move", @"operationType",
+ nil]];
+ if (previousObjectRequest)
+ [objectRequest addDependency:previousObjectRequest];
+ previousObjectRequest = objectRequest;
+ [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
+ }
+ }
+ }
+ }
+ }];
+ [moveQueue addOperation:operation];
+ }
+ }
+}
+
+- (void)menuCut:(NSMenuItem *)sender {
+ self.clipboardNodes = (NSArray *)[sender representedObject];
+ self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
+ self.clipboardCopy = NO;
+}
+
+- (void)menuCopy:(NSMenuItem *)sender {
+ self.clipboardNodes = (NSArray *)[sender representedObject];
+ self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
+ self.clipboardCopy = YES;
+}
+
+- (void)menuPaste:(NSMenuItem *)sender {
+ if (!clipboardNodes || ![clipboardNodes count])
+ return;
+ PithosNode *dropNode = (PithosNode *)[sender representedObject];
+ NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
+ if (clipboardCopy) {
+ [self cpyNodes:localClipboardNodes toNode:dropNode];
+ } else if (![dropNode isEqualTo:clipboardParentNode]) {
+ self.clipboardNodes = nil;
+ self.clipboardParentNode = nil;
+ [self moveNodes:localClipboardNodes toNode:dropNode];
+ }
+}
+
+#pragma mark -
+#pragma mark PithosActivityFacilityDelegate
+
+- (void)activityUpdate:(NSDictionary *)info {
+ NSString *message = [info objectForKey:@"message"];
+ NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
+// NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
+ NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
+ NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
+ NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
+ NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
+ NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
+ if (runningActivitiesCount && totalBytes) {
+ [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
+ [activityProgressIndicator startAnimation:self];
+ } else {
+ [activityProgressIndicator setDoubleValue:1.0];
+ [activityProgressIndicator stopAnimation:self];
+ }
+
+ if (!message)
+ message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
+ [activityTextField setStringValue:message];
}
@end
\ No newline at end of file