2 // PithosBrowserController.m
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosSharingAccountsNode.h"
45 #import "PithosEmptyNode.h"
46 #import "ImageAndTextCell.h"
47 #import "FileSystemBrowserCell.h"
48 #import "ASINetworkQueue.h"
49 #import "ASIPithosRequest.h"
51 #import "ASIPithosContainerRequest.h"
52 #import "ASIPithosObjectRequest.h"
53 #import "ASIPithosAccount.h"
54 #import "ASIPithosContainer.h"
55 #import "ASIPithosObject.h"
56 #import "PithosUtilities.h"
57 #import "UsingSizeTransformer.h"
59 @interface PithosBrowserCell : FileSystemBrowserCell {}
62 @implementation PithosBrowserCell
65 if ((self = [super init])) {
66 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
67 [self setEditable:YES];
72 - (void)setObjectValue:(id)object {
73 if ([object isKindOfClass:[PithosNode class]]) {
74 PithosNode *node = (PithosNode *)object;
75 [self setStringValue:node.displayName];
76 [self setImage:node.icon];
78 [super setObjectValue:object];
84 @interface PithosOutlineViewCell : ImageAndTextCell {}
87 @implementation PithosOutlineViewCell
89 - (void)setObjectValue:(id)object {
90 if ([object isKindOfClass:[PithosNode class]]) {
91 PithosNode *node = (PithosNode *)object;
92 [self setStringValue:node.displayName];
93 [self setImage:node.icon];
94 [self setEditable:NO];
96 [super setObjectValue:object];
102 @interface PithosBrowserController (Private)
103 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
104 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
105 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
108 @implementation PithosBrowserController
110 @synthesize accountNode;
111 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu;
112 @synthesize draggedNodes, draggedParentNode;
113 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
114 @synthesize activityTextField, activityProgressIndicator;
117 #pragma Object Lifecycle
120 return [super initWithWindowNibName:@"PithosBrowserController"];
123 - (void)windowDidLoad {
124 [super windowDidLoad];
125 if (browser && !browserInitialized) {
126 browserInitialized = YES;
131 - (void)initBrowser {
132 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
133 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
134 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
136 [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
137 [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
138 [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
140 [browser setCellClass:[PithosBrowserCell class]];
141 [browser setAllowsBranchSelection:YES];
142 [browser setAllowsMultipleSelection:YES];
143 [browser setAllowsEmptySelection:YES];
144 [browser setAllowsTypeSelect:YES];
146 moveNetworkQueue = [[ASINetworkQueue alloc] init];
147 moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
148 // moveNetworkQueue.maxConcurrentOperationCount = 1;
149 copyNetworkQueue = [[ASINetworkQueue alloc] init];
150 copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
151 // copyNetworkQueue.maxConcurrentOperationCount = 1;
152 deleteNetworkQueue = [[ASINetworkQueue alloc] init];
153 deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
154 // deleteNetworkQueue.maxConcurrentOperationCount = 1;
155 uploadNetworkQueue = [[ASINetworkQueue alloc] init];
156 uploadNetworkQueue.showAccurateProgress = YES;
157 uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
158 // uploadNetworkQueue.maxConcurrentOperationCount = 1;
159 downloadNetworkQueue = [[ASINetworkQueue alloc] init];
160 downloadNetworkQueue.showAccurateProgress = YES;
161 downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
162 // downloadNetworkQueue.maxConcurrentOperationCount = 1;
164 moveQueue = [[NSOperationQueue alloc] init];
165 [moveQueue setSuspended:YES];
166 moveQueue.name = @"gr.grnet.pithos.MoveQueue";
167 // moveQueue.maxConcurrentOperationCount = 1;
168 copyQueue = [[NSOperationQueue alloc] init];
169 [copyQueue setSuspended:YES];
170 copyQueue.name = @"gr.grnet.pithos.CopyQueue";
171 // copyQueue.maxConcurrentOperationCount = 1;
172 deleteQueue = [[NSOperationQueue alloc] init];
173 [deleteQueue setSuspended:YES];
174 deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
175 // deleteQueue.maxConcurrentOperationCount = 1;
176 uploadQueue = [[NSOperationQueue alloc] init];
177 [uploadQueue setSuspended:YES];
178 uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
179 // uploadQueue.maxConcurrentOperationCount = 1;
180 downloadQueue = [[NSOperationQueue alloc] init];
181 [downloadQueue setSuspended:YES];
182 downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
183 // downloadQueue.maxConcurrentOperationCount = 1;
185 moveCallbackQueue = [[NSOperationQueue alloc] init];
186 [moveCallbackQueue setSuspended:YES];
187 moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
188 // moveCallbackQueue.maxConcurrentOperationCount = 1;
189 copyCallbackQueue = [[NSOperationQueue alloc] init];
190 [copyCallbackQueue setSuspended:YES];
191 copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
192 // copyCallbackQueue.maxConcurrentOperationCount = 1;
193 deleteCallbackQueue = [[NSOperationQueue alloc] init];
194 [deleteCallbackQueue setSuspended:YES];
195 deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
196 // deleteCallbackQueue.maxConcurrentOperationCount = 1;
197 uploadCallbackQueue = [[NSOperationQueue alloc] init];
198 [uploadCallbackQueue setSuspended:YES];
199 uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
200 // uploadCallbackQueue.maxConcurrentOperationCount = 1;
201 downloadCallbackQueue = [[NSOperationQueue alloc] init];
202 [downloadCallbackQueue setSuspended:YES];
203 downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
204 // downloadCallbackQueue.maxConcurrentOperationCount = 1;
206 [activityProgressIndicator setUsesThreadedAnimation:YES];
207 [activityProgressIndicator setMinValue:0.0];
208 [activityProgressIndicator setMaxValue:1.0];
209 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
211 self.accountNode = [[[PithosAccountNode alloc] initWithPithos:pithos] autorelease];
212 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
213 containersNodeChildren = [[NSMutableArray alloc] init];
214 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
215 mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
216 mySharedNode.displayName = @"my shared";
217 mySharedNode.shared = YES;
218 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
219 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
220 othersSharedNode.displayName = @"others shared";
221 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
223 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
225 // Register for updates
226 // PithosContainerNode updates browser nodes
227 [[NSNotificationCenter defaultCenter] addObserver:self
228 selector:@selector(pithosNodeChildrenUpdated:)
229 name:@"PithosContainerNodeChildrenUpdated"
231 // PithosSubdirNode updates browser nodes
232 [[NSNotificationCenter defaultCenter] addObserver:self
233 selector:@selector(pithosNodeChildrenUpdated:)
234 name:@"PithosSubdirNodeChildrenUpdated"
236 // PithosAccountNode accountNode updates outlineView container nodes
237 [[NSNotificationCenter defaultCenter] addObserver:self
238 selector:@selector(pithosAccountNodeChildrenUpdated:)
239 name:@"PithosAccountNodeChildrenUpdated"
241 // PithosAccountNode other than accountNode updates nodes
242 [[NSNotificationCenter defaultCenter] addObserver:self
243 selector:@selector(pithosNodeChildrenUpdated:)
244 name:@"PithosAccountNodeChildrenUpdated"
246 // PithosSharingAccountsNode othersSharedNode updates browser nodes
247 [[NSNotificationCenter defaultCenter] addObserver:self
248 selector:@selector(pithosNodeChildrenUpdated:)
249 name:@"PithosSharingAccountsNodeChildrenUpdated"
250 object:othersSharedNode];
251 // Request for browser refresh
252 [[NSNotificationCenter defaultCenter] addObserver:self
253 selector:@selector(pithosBrowserRefreshNeeded:)
254 name:@"PithosBrowserRefreshNeeeded"
258 - (void)resetBrowser {
259 @synchronized(self) {
264 [refreshTimer invalidate];
265 [refreshTimer release];
267 [moveNetworkQueue reset];
268 [copyNetworkQueue reset];
269 [deleteNetworkQueue reset];
270 [uploadNetworkQueue reset];
271 [downloadNetworkQueue reset];
273 [moveQueue cancelAllOperations];
274 [moveQueue setSuspended:YES];
275 [copyQueue cancelAllOperations];
276 [copyQueue setSuspended:YES];
277 [deleteQueue cancelAllOperations];
278 [deleteQueue setSuspended:YES];
279 [uploadQueue cancelAllOperations];
280 [uploadQueue setSuspended:YES];
281 [downloadQueue cancelAllOperations];
282 [downloadQueue setSuspended:YES];
284 [moveCallbackQueue cancelAllOperations];
285 [moveCallbackQueue setSuspended:YES];
286 [copyCallbackQueue cancelAllOperations];
287 [copyCallbackQueue setSuspended:YES];
288 [deleteCallbackQueue cancelAllOperations];
289 [deleteCallbackQueue setSuspended:YES];
290 [uploadCallbackQueue cancelAllOperations];
291 [uploadCallbackQueue setSuspended:YES];
292 [downloadCallbackQueue cancelAllOperations];
293 [downloadCallbackQueue setSuspended:YES];
296 [browser loadColumnZero];
297 [containersNodeChildren removeAllObjects];
298 [outlineView reloadData];
299 // Expand the folder outline view
300 [outlineView expandItem:nil expandChildren:YES];
301 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
303 activityFacility.delegate = nil;
304 [activityProgressIndicator setDoubleValue:1.0];
305 [activityProgressIndicator stopAnimation:self];
307 @synchronized(self) {
312 - (void)startBrowser {
313 @synchronized(self) {
318 // In the improbable case of leftover operations
319 [moveNetworkQueue reset];
320 [copyNetworkQueue reset];
321 [deleteNetworkQueue reset];
322 [uploadNetworkQueue reset];
323 [downloadNetworkQueue reset];
324 [moveQueue cancelAllOperations];
325 [copyQueue cancelAllOperations];
326 [deleteQueue cancelAllOperations];
327 [uploadQueue cancelAllOperations];
328 [downloadQueue cancelAllOperations];
329 [moveCallbackQueue cancelAllOperations];
330 [copyCallbackQueue cancelAllOperations];
331 [deleteCallbackQueue cancelAllOperations];
332 [uploadCallbackQueue cancelAllOperations];
333 [downloadCallbackQueue cancelAllOperations];
335 [moveNetworkQueue go];
336 [copyNetworkQueue go];
337 [deleteNetworkQueue go];
338 [uploadNetworkQueue go];
339 [downloadNetworkQueue go];
340 [moveQueue setSuspended:NO];
341 [copyQueue setSuspended:NO];
342 [deleteQueue setSuspended:NO];
343 [uploadQueue setSuspended:NO];
344 [downloadQueue setSuspended:NO];
345 [moveCallbackQueue setSuspended:NO];
346 [copyCallbackQueue setSuspended:NO];
347 [deleteCallbackQueue setSuspended:NO];
348 [uploadCallbackQueue setSuspended:NO];
349 [downloadCallbackQueue setSuspended:NO];
351 accountNode.pithos = pithos;
352 [accountNode forceRefresh];
353 mySharedNode.pithos = pithos;
354 [mySharedNode forceRefresh];
355 othersSharedNode.pithos = pithos;
356 [othersSharedNode forceRefresh];
358 // [activityFacility reset];
359 activityFacility.delegate = self;
361 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30
363 selector:@selector(forceRefresh:)
365 repeats:YES] retain];
366 @synchronized(self) {
371 - (BOOL)operationsPending {
372 return ([moveNetworkQueue operationCount] ||
373 [copyNetworkQueue operationCount] ||
374 [deleteNetworkQueue operationCount] ||
375 [uploadNetworkQueue operationCount] ||
376 [downloadNetworkQueue operationCount] ||
377 [moveQueue operationCount] ||
378 [copyQueue operationCount] ||
379 [deleteQueue operationCount] ||
380 [uploadQueue operationCount] ||
381 [downloadQueue operationCount] ||
382 [moveCallbackQueue operationCount] ||
383 [copyCallbackQueue operationCount] ||
384 [deleteCallbackQueue operationCount] ||
385 [uploadCallbackQueue operationCount] ||
386 [downloadCallbackQueue operationCount]);
390 [[NSNotificationCenter defaultCenter] removeObserver:self];
394 [deleteQueue release];
395 [uploadQueue release];
396 [downloadQueue release];
397 [moveNetworkQueue release];
398 [copyNetworkQueue release];
399 [deleteNetworkQueue release];
400 [uploadNetworkQueue release];
401 [downloadNetworkQueue release];
402 [clipboardParentNode release];
403 [clipboardNodes release];
404 [draggedParentNode release];
405 [draggedNodes release];
406 [sharedPreviewController release];
407 [othersSharedNode release];
408 [mySharedNode release];
409 [sharedNode release];
410 [containersNodeChildren release];
411 [containersNode release];
412 [accountNode release];
418 - (void)setPithos:(ASIPithos *)aPithos {
420 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
421 ![aPithos.authToken isEqualToString:pithos.authToken] ||
422 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
423 ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
426 pithos = [aPithos retain];
436 #pragma mark Observers
438 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
439 PithosNode *node = (PithosNode *)[notification object];
440 if (node == accountNode)
442 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
443 NSInteger lastColumn = [browser lastColumn];
444 for (NSInteger column = lastColumn; column >= 0; column--) {
445 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
446 [browser reloadColumn:column];
452 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
453 BOOL containerPithosFound = NO;
454 BOOL containerTrashFound = NO;
455 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
456 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
457 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
458 [removedContainersNodeChildren addIndex:i];
460 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
461 for (PithosContainerNode *containerNode in accountNode.children) {
462 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
463 if (![containersNodeChildren containsObject:containerNode])
464 [containersNodeChildren insertObject:containerNode atIndex:0];
465 containerPithosFound = YES;
466 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
467 NSUInteger insertIndex = 1;
468 if (!containerPithosFound)
470 if (![containersNodeChildren containsObject:containerNode])
471 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
472 containerTrashFound = YES;
473 } else if (![containersNodeChildren containsObject:containerNode]) {
474 [containersNodeChildren addObject:containerNode];
477 BOOL refreshAccountNode = NO;
478 if (!containerPithosFound) {
479 // Create pithos node
480 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
481 containerName:@"pithos"];
482 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
484 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
485 if ([containerRequest error]) {
486 dispatch_async(dispatch_get_main_queue(), ^{
487 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
490 refreshAccountNode = YES;
493 if (!containerTrashFound) {
495 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
496 containerName:@"trash"];
497 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
499 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
500 if ([containerRequest error]) {
501 dispatch_async(dispatch_get_main_queue(), ^{
502 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
505 refreshAccountNode = YES;
509 if (refreshAccountNode)
510 [accountNode refresh];
512 [outlineView reloadData];
514 // Expand the folder outline view
515 [outlineView expandItem:nil expandChildren:YES];
517 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
518 rootNode = [containersNodeChildren objectAtIndex:0];
519 [browser loadColumnZero];
526 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
533 - (IBAction)forceRefresh:(id)sender {
535 [accountNode forceRefresh];
536 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
537 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
538 node.forcedRefresh = YES;
539 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
541 [browser validateVisibleColumns];
544 - (IBAction)refresh:(id)sender {
545 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
546 [self forceRefresh:sender];
549 [accountNode refresh];
550 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
551 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
553 [browser validateVisibleColumns];
558 #pragma mark NSBrowserDelegate
560 - (id)rootItemForBrowser:(NSBrowser *)browser {
564 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
565 PithosNode *node = (PithosNode *)item;
566 return node.children.count;
569 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
570 PithosNode *node = (PithosNode *)item;
571 return [node.children objectAtIndex:index];
574 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
575 PithosNode *node = (PithosNode *)item;
576 return node.isLeafItem;
579 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
580 PithosNode *node = (PithosNode *)item;
584 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
585 if (sharedPreviewController == nil)
586 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
587 return sharedPreviewController;
590 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
591 // if (!forUserResize) {
592 // id item = [browser parentForItemsInColumn:columnIndex];
593 // if ([self browser:browser isLeafItem:item]) {
594 // suggestedWidth = 200;
597 // return suggestedWidth;
600 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
606 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
607 PithosNode *node = (PithosNode *)item;
608 if (node.shared || node.sharingAccount ||
609 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
614 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
615 PithosNode *node = (PithosNode *)item;
616 NSString *newName = (NSString *)object;
617 NSUInteger newNameLength = [newName length];
618 NSRange firstSlashRange = [newName rangeOfString:@"/"];
619 if ((newNameLength == 0) ||
620 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
621 ([newName isEqualToString:node.displayName])) {
624 if (([node class] == [PithosObjectNode class]) ||
625 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
626 // Operation: Rename (move) an object or subdir/ node
627 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
628 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
629 if (operation.isCancelled) {
633 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
634 if ([newName hasSuffix:@"/"])
635 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
636 NSError *error = nil;
638 if ([PithosUtilities objectExistsAtPithos:pithos
639 containerName:node.pithosContainer.name
640 objectName:destinationObjectName
642 isDirectory:&isDirectory
643 sharingAccount:nil]) {
644 dispatch_async(dispatch_get_main_queue(), ^{
645 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
646 [alert setMessageText:@"Name Taken"];
647 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
648 [alert addButtonWithTitle:@"OK"];
657 if (operation.isCancelled) {
661 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
662 containerName:node.pithosContainer.name
663 objectName:node.pithosObject.name
664 destinationContainerName:node.pithosContainer.name
665 destinationObjectName:destinationObjectName
667 if (!operation.isCancelled && objectRequest) {
668 objectRequest.delegate = self;
669 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
670 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
671 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
672 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
673 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
674 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
675 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
676 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
677 message:messagePrefix];
678 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
679 [NSDictionary dictionaryWithObjectsAndKeys:
680 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
681 [NSNumber numberWithBool:YES], @"refresh",
682 activity, @"activity",
683 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
684 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
685 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
686 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
687 [NSNumber numberWithUnsignedInteger:10], @"retries",
688 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
689 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
690 moveNetworkQueue, @"networkQueue",
691 @"move", @"operationType",
693 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
697 [moveQueue addOperation:operation];
698 } else if ([node class] == [PithosSubdirNode class]) {
699 if (firstSlashRange.length == 1)
701 // Operation: Rename (move) a subdir node and its descendants
702 // The resulting ASIPithosObjectRequests are chained through dependencies
703 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
704 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
705 if (operation.isCancelled) {
709 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
710 NSError *error = nil;
712 if ([PithosUtilities objectExistsAtPithos:pithos
713 containerName:node.pithosContainer.name
714 objectName:destinationObjectName
716 isDirectory:&isDirectory
717 sharingAccount:nil]) {
718 dispatch_async(dispatch_get_main_queue(), ^{
719 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
720 [alert setMessageText:@"Name Taken"];
721 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
722 [alert addButtonWithTitle:@"OK"];
731 if (operation.isCancelled) {
735 if (node.pithosObject.subdir)
736 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
737 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
738 containerName:node.pithosContainer.name
739 objectName:node.pithosObject.name
740 destinationContainerName:node.pithosContainer.name
741 destinationObjectName:destinationObjectName
743 if (!operation.isCancelled && objectRequests) {
744 ASIPithosObjectRequest *previousObjectRequest = nil;
745 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
746 if (operation.isCancelled) {
750 objectRequest.delegate = self;
751 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
752 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
753 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
754 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
755 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
756 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
757 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
758 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
759 message:messagePrefix];
760 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
761 [NSDictionary dictionaryWithObjectsAndKeys:
762 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
763 [NSNumber numberWithBool:YES], @"refresh",
764 activity, @"activity",
765 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
766 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
767 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
768 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
769 [NSNumber numberWithUnsignedInteger:10], @"retries",
770 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
771 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
772 moveNetworkQueue, @"networkQueue",
773 @"move", @"operationType",
775 if (previousObjectRequest)
776 [objectRequest addDependency:previousObjectRequest];
777 previousObjectRequest = objectRequest;
778 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
783 [moveQueue addOperation:operation];
787 #pragma mark Drag and Drop source
789 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
790 withEvent:(NSEvent *)event {
791 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
792 __block BOOL result = YES;
793 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
794 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
795 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
803 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
804 toPasteboard:(NSPasteboard *)pasteboard {
805 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
806 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
807 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
808 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
809 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
810 [propertyList addObject:[node.pithosObject.name pathExtension]];
811 [nodes addObject:node];
814 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
815 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
816 self.draggedNodes = nodes;
817 self.draggedParentNode = [browser parentForItemsInColumn:column];
821 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
822 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
823 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
824 for (PithosNode *node in draggedNodes) {
825 // If the node is a subdir ask if the whole tree should be downloaded
826 if ([node class] == [PithosSubdirNode class]) {
827 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
828 [alert setMessageText:@"Download directory"];
829 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
830 [alert addButtonWithTitle:@"OK"];
831 [alert addButtonWithTitle:@"Cancel"];
832 NSInteger choice = [alert runModal];
833 if (choice == NSAlertFirstButtonReturn) {
834 // Operation: Download a subdir node and its descendants
835 // The resulting ASIPithosObjectRequests are chained through dependencies
836 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
837 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
838 if (operation.isCancelled) {
842 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
843 containerName:node.pithosContainer.name
844 objectName:node.pithosObject.name
845 toDirectory:[dropDestination path]
847 sharingAccount:node.sharingAccount];
848 if (!operation.isCancelled && objectRequests) {
849 ASIPithosObjectRequest *previousObjectRequest = nil;
850 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
851 if (operation.isCancelled) {
855 [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]];
856 objectRequest.delegate = self;
857 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
858 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
859 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
860 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
861 message:[messagePrefix stringByAppendingString:@" (0%)"]
862 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
864 dispatch_async(dispatch_get_main_queue(), ^{
865 [activityFacility updateActivity:activity withMessage:activity.message];
867 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
868 [NSDictionary dictionaryWithObjectsAndKeys:
869 activity, @"activity",
870 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
871 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
872 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
873 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
874 [NSNumber numberWithUnsignedInteger:10], @"retries",
875 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
876 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
877 downloadNetworkQueue, @"networkQueue",
878 @"download", @"operationType",
880 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
881 [activityFacility updateActivity:activity
882 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
883 totalBytes:activity.totalBytes
884 currentBytes:(activity.currentBytes + size)];
886 if (previousObjectRequest)
887 [objectRequest addDependency:previousObjectRequest];
888 previousObjectRequest = objectRequest;
889 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
894 [downloadQueue addOperation:operation];
897 // Operation: Download an object node
898 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
899 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
900 if (operation.isCancelled) {
904 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
905 containerName:node.pithosContainer.name
906 objectName:node.pithosObject.name
907 toDirectory:[dropDestination path]
909 sharingAccount:node.sharingAccount];
910 if (!operation.isCancelled && objectRequest) {
911 [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]];
912 objectRequest.delegate = self;
913 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
914 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
915 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
916 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
917 message:[messagePrefix stringByAppendingString:@" (0%)"]
918 totalBytes:node.pithosObject.bytes
920 dispatch_async(dispatch_get_main_queue(), ^{
921 [activityFacility updateActivity:activity withMessage:activity.message];
923 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
924 [NSDictionary dictionaryWithObjectsAndKeys:
925 activity, @"activity",
926 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
927 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
928 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
929 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
930 [NSNumber numberWithUnsignedInteger:10], @"retries",
931 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
932 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
933 downloadNetworkQueue, @"networkQueue",
934 @"download", @"operationType",
936 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
937 [activityFacility updateActivity:activity
938 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
939 totalBytes:activity.totalBytes
940 currentBytes:(activity.currentBytes + size)];
942 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
946 [downloadQueue addOperation:operation];
952 #pragma mark Drag and Drop destination
954 - (NSDragOperation)browser:aBrowser
955 validateDrop:(id<NSDraggingInfo>)info
956 proposedRow:(NSInteger *)row
957 column:(NSInteger *)column
958 dropOperation:(NSBrowserDropOperation *)dropOperation {
959 NSDragOperation result = NSDragOperationNone;
960 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
961 // For a drop above, the drop is redirected to the parent item
962 if (*dropOperation == NSBrowserDropAbove)
964 // Only allow dropping in folders
966 PithosNode *dropNode;
968 // Check if the node is not a folder and if so redirect to the parent item
969 dropNode = [browser itemAtRow:*row inColumn:*column];
970 if ([dropNode class] == [PithosObjectNode class])
974 dropNode = [browser parentForItemsInColumn:*column];
976 if (!dropNode.shared &&
977 (!dropNode.sharingAccount ||
978 ([dropNode class] == [PithosSubdirNode class]) ||
979 ([dropNode class] == [PithosContainerNode class]))) {
980 *dropOperation = NSBrowserDropOn;
981 result = NSDragOperationCopy;
984 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
985 // For a drop above, the drop is redirected to the parent item
986 if (*dropOperation == NSBrowserDropAbove)
988 // Only allow dropping in folders
990 PithosNode *dropNode;
992 // Check if the node is not a folder and if so redirect to the parent item
993 dropNode = [browser itemAtRow:*row inColumn:*column];
994 if ([dropNode class] == [PithosObjectNode class])
998 dropNode = [browser parentForItemsInColumn:*column];
1000 if (!dropNode.shared && !dropNode.sharingAccount) {
1001 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
1002 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
1003 if ((([dropNode class] == [PithosContainerNode class]) ||
1004 dropNode.pithosObject.subdir ||
1005 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
1006 ![dropNode isEqualTo:draggedParentNode]) {
1007 // ![dropNode isEqualTo:draggedParentNode] &&
1008 // ![draggedNodes containsObject:dropNode]) {
1009 result = NSDragOperationMove;
1011 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
1012 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
1013 if (([dropNode class] == [PithosContainerNode class]) ||
1014 dropNode.pithosObject.subdir ||
1015 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
1016 result = NSDragOperationCopy;
1025 - (BOOL)browser:(NSBrowser *)aBrowser
1026 acceptDrop:(id<NSDraggingInfo>)info
1027 atRow:(NSInteger)row
1028 column:(NSInteger)column
1029 dropOperation:(NSBrowserDropOperation)dropOperation {
1030 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1031 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1032 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1033 if ((column != -1) && (filenames != nil)) {
1036 node = [browser itemAtRow:row inColumn:column];
1038 node = [browser parentForItemsInColumn:column];
1039 NSLog(@"drag in node: %@", node.url);
1040 return [self uploadFiles:filenames toNode:node];
1042 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1043 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1044 if ((column != -1) && (draggedNodes != nil)) {
1047 node = [browser itemAtRow:row inColumn:column];
1049 node = [browser parentForItemsInColumn:column];
1050 NSLog(@"drag local node: %@", node.url);
1051 if ([info draggingSourceOperationMask] & NSDragOperationMove)
1052 return [self moveNodes:draggedNodes toNode:node];
1053 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1054 return [self copyNodes:draggedNodes toNode:node];
1061 #pragma mark Drag and Drop methods
1063 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1064 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1066 NSFileManager *fileManager = [NSFileManager defaultManager];
1067 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1068 NSString *objectNamePrefix;
1069 if ([destinationNode class] == [PithosSubdirNode class])
1070 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1072 objectNamePrefix = [NSString string];
1073 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1074 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1075 containerName:containerName];
1076 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1078 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1079 if ([containerRequest error]) {
1080 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1082 } else if (containerRequest.responseStatusCode != 204) {
1083 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1086 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1087 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1089 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1090 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1092 for (NSString *filePath in filenames) {
1094 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1097 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1098 // Operation: Upload a local file
1099 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1100 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1101 if (operation.isCancelled) {
1105 NSError *error = nil;
1106 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1107 if (contentType == nil)
1108 contentType = @"application/octet-stream";
1110 NSLog(@"contentType detection error: %@", error);
1111 NSArray *hashes = nil;
1112 if (operation.isCancelled) {
1116 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1117 containerName:containerName
1118 objectName:objectName
1119 contentType:contentType
1125 sharingAccount:destinationNode.sharingAccount];
1126 if (!operation.isCancelled && objectRequest) {
1127 objectRequest.delegate = self;
1128 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1129 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1130 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1131 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1132 message:[messagePrefix stringByAppendingString:@" (0%)"]
1133 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1135 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1136 [NSDictionary dictionaryWithObjectsAndKeys:
1137 containerName, @"containerName",
1138 objectName, @"objectName",
1139 contentType, @"contentType",
1140 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1141 blockHash, @"blockHash",
1142 filePath, @"filePath",
1144 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1145 [NSNumber numberWithBool:YES], @"refresh",
1146 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1147 activity, @"activity",
1148 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1149 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1150 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1151 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1152 [NSNumber numberWithUnsignedInteger:10], @"retries",
1153 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1154 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1155 uploadNetworkQueue, @"networkQueue",
1156 @"upload", @"operationType",
1158 if (destinationNode.sharingAccount)
1159 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1160 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1164 [uploadQueue addOperation:operation];
1166 // Upload directory, confirm first
1167 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1168 [alert setMessageText:@"Upload directory"];
1169 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1170 [alert addButtonWithTitle:@"OK"];
1171 [alert addButtonWithTitle:@"Cancel"];
1172 NSInteger choice = [alert runModal];
1173 if (choice == NSAlertFirstButtonReturn) {
1174 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1175 // Operation: Upload a local directory and its descendants
1176 // The resulting ASIPithosObjectRequests are chained through dependencies
1177 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1178 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1179 if (operation.isCancelled) {
1183 NSMutableArray *objectNames = nil;
1184 NSMutableArray *contentTypes = nil;
1185 NSMutableArray *filePaths = nil;
1186 NSMutableArray *hashesArrays = nil;
1187 NSMutableArray *directoryObjectRequests = nil;
1188 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1189 containerName:containerName
1190 objectName:objectName
1193 forDirectory:filePath
1195 objectNames:&objectNames
1196 contentTypes:&contentTypes
1197 filePaths:&filePaths
1198 hashesArrays:&hashesArrays
1199 directoryObjectRequests:&directoryObjectRequests
1200 sharingAccount:destinationNode.sharingAccount];
1201 if (operation.isCancelled) {
1205 ASIPithosObjectRequest *previousObjectRequest = nil;
1206 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1207 if (operation.isCancelled) {
1211 objectRequest.delegate = self;
1212 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1213 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1214 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1215 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1216 message:messagePrefix];
1217 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1218 [NSDictionary dictionaryWithObjectsAndKeys:
1219 [NSNumber numberWithBool:YES], @"refresh",
1220 activity, @"activity",
1221 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1222 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1223 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1224 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1225 [NSNumber numberWithUnsignedInteger:10], @"retries",
1226 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1227 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1228 uploadNetworkQueue, @"networkQueue",
1229 @"upload", @"queue",
1231 if (previousObjectRequest)
1232 [objectRequest addDependency:previousObjectRequest];
1233 previousObjectRequest = objectRequest;
1234 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1236 if (!operation.isCancelled && objectRequests) {
1237 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1238 if (operation.isCancelled) {
1242 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1243 objectRequest.delegate = self;
1244 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1245 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1246 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1247 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1248 message:[messagePrefix stringByAppendingString:@" (0%)"]
1249 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1251 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1252 [NSDictionary dictionaryWithObjectsAndKeys:
1253 containerName, @"containerName",
1254 [objectNames objectAtIndex:i], @"objectName",
1255 [contentTypes objectAtIndex:i], @"contentType",
1256 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1257 blockHash, @"blockHash",
1258 [filePaths objectAtIndex:i], @"filePath",
1259 [hashesArrays objectAtIndex:i], @"hashes",
1260 [NSNumber numberWithBool:YES], @"refresh",
1261 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1262 activity, @"activity",
1263 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1264 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1265 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1266 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1267 [NSNumber numberWithUnsignedInteger:10], @"retries",
1268 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1269 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1270 uploadNetworkQueue, @"networkQueue",
1271 @"upload", @"queue",
1273 if (destinationNode.sharingAccount)
1274 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1275 if (previousObjectRequest)
1276 [objectRequest addDependency:previousObjectRequest];
1277 previousObjectRequest = objectRequest;
1278 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1283 [uploadQueue addOperation:operation];
1291 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1292 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1293 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1295 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1296 NSString *objectNamePrefix;
1297 if ([destinationNode class] == [PithosSubdirNode class])
1298 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1300 objectNamePrefix = [NSString string];
1302 for (PithosNode *node in nodes) {
1303 if (([node class] == [PithosObjectNode class]) ||
1304 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1305 // Operation: Move an object or subdir/ node
1306 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1307 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1308 if (operation.isCancelled) {
1312 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1313 if ([node.pithosObject.name hasSuffix:@"/"])
1314 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1315 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1316 containerName:node.pithosContainer.name
1317 objectName:node.pithosObject.name
1318 destinationContainerName:containerName
1319 destinationObjectName:destinationObjectName
1321 if (!operation.isCancelled && objectRequest) {
1322 objectRequest.delegate = self;
1323 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1324 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1325 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1326 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1327 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1328 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1329 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1330 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1331 message:messagePrefix];
1332 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1333 [NSDictionary dictionaryWithObjectsAndKeys:
1334 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1335 activity, @"activity",
1336 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1337 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1338 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1339 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1340 [NSNumber numberWithUnsignedInteger:10], @"retries",
1341 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1342 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1343 moveNetworkQueue, @"networkQueue",
1344 @"move", @"operationType",
1346 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1350 [moveQueue addOperation:operation];
1351 } else if ([node class] == [PithosSubdirNode class]) {
1352 // Operation: Move a subdir node and its descendants
1353 // The resulting ASIPithosObjectRequests are chained through dependencies
1354 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1355 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1356 if (operation.isCancelled) {
1360 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1361 if (node.pithosObject.subdir)
1362 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1363 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1364 containerName:node.pithosContainer.name
1365 objectName:node.pithosObject.name
1366 destinationContainerName:containerName
1367 destinationObjectName:destinationObjectName
1369 if (!operation.isCancelled && objectRequests) {
1370 ASIPithosObjectRequest *previousObjectRequest = nil;
1371 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1372 if (operation.isCancelled) {
1376 objectRequest.delegate = self;
1377 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1378 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1379 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1380 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1381 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1382 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1383 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1384 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1385 message:messagePrefix];
1386 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1387 [NSDictionary dictionaryWithObjectsAndKeys:
1388 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1389 [NSNumber numberWithBool:YES], @"refresh",
1390 activity, @"activity",
1391 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1392 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1393 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1394 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1395 [NSNumber numberWithUnsignedInteger:10], @"retries",
1396 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1397 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1398 moveNetworkQueue, @"networkQueue",
1399 @"move", @"operationType",
1401 if (previousObjectRequest)
1402 [objectRequest addDependency:previousObjectRequest];
1403 previousObjectRequest = objectRequest;
1404 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1409 [moveQueue addOperation:operation];
1415 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1416 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1417 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1419 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1420 NSString *objectNamePrefix;
1421 if ([destinationNode class] == [PithosSubdirNode class])
1422 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1424 objectNamePrefix = [NSString string];
1426 for (PithosNode *node in nodes) {
1427 if (([node class] == [PithosObjectNode class]) ||
1428 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1429 // Operation: Copy an object or subdir/ node
1430 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1431 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1432 if (operation.isCancelled) {
1436 NSString *destinationObjectName;
1437 if (![destinationNode isEqualTo:node.parent]) {
1438 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1439 if ([node.pithosObject.name hasSuffix:@"/"])
1440 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1442 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1443 containerName:containerName
1444 objectName:node.pithosObject.name];
1446 if (operation.isCancelled) {
1450 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos
1451 containerName:node.pithosContainer.name
1452 objectName:node.pithosObject.name
1453 destinationContainerName:containerName
1454 destinationObjectName:destinationObjectName
1456 sharingAccount:node.sharingAccount];
1457 if (!operation.isCancelled && objectRequest) {
1458 objectRequest.delegate = self;
1459 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1460 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1461 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1462 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1463 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1464 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1465 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1466 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1467 message:messagePrefix];
1468 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1469 [NSDictionary dictionaryWithObjectsAndKeys:
1470 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1471 activity, @"activity",
1472 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1473 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1474 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1475 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1476 [NSNumber numberWithUnsignedInteger:10], @"retries",
1477 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1478 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1479 copyNetworkQueue, @"networkQueue",
1480 @"copy", @"operationType",
1482 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1486 [copyQueue addOperation:operation];
1487 } else if ([node class] == [PithosSubdirNode class]) {
1488 // Operation: Copy a subdir node and its descendants
1489 // The resulting ASIPithosObjectRequests are chained through dependencies
1490 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1491 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1492 if (operation.isCancelled) {
1496 NSString *destinationObjectName;
1497 if (![destinationNode isEqualTo:node.parent]) {
1498 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1499 if (node.pithosObject.subdir)
1500 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1502 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1503 containerName:containerName
1504 subdirName:node.pithosObject.name];
1506 if (operation.isCancelled) {
1510 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos
1511 containerName:node.pithosContainer.name
1512 objectName:node.pithosObject.name
1513 destinationContainerName:containerName
1514 destinationObjectName:destinationObjectName
1516 sharingAccount:node.sharingAccount];
1517 if (!operation.isCancelled && objectRequests) {
1518 ASIPithosObjectRequest *previousObjectRequest = nil;
1519 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1520 if (operation.isCancelled) {
1524 objectRequest.delegate = self;
1525 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1526 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1527 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1528 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1529 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1530 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1531 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1532 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1533 message:messagePrefix];
1534 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1535 [NSDictionary dictionaryWithObjectsAndKeys:
1536 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1537 activity, @"activity",
1538 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1539 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1540 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1541 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1542 [NSNumber numberWithUnsignedInteger:10], @"retries",
1543 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1544 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1545 copyNetworkQueue, @"networkQueue",
1546 @"copy", @"operationType",
1548 if (previousObjectRequest)
1549 [objectRequest addDependency:previousObjectRequest];
1550 previousObjectRequest = objectRequest;
1551 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1556 [copyQueue addOperation:operation];
1563 #pragma mark ASIHTTPRequestDelegate
1565 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1566 NSOperationQueue *callbackQueue;
1567 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1568 if ([operationType isEqualToString:@"move"])
1569 callbackQueue = moveCallbackQueue;
1570 else if ([operationType isEqualToString:@"copy"])
1571 callbackQueue = copyCallbackQueue;
1572 else if ([operationType isEqualToString:@"delete"])
1573 callbackQueue = deleteCallbackQueue;
1574 else if ([operationType isEqualToString:@"upload"])
1575 callbackQueue = uploadCallbackQueue;
1576 else if ([operationType isEqualToString:@"download"])
1577 callbackQueue = downloadCallbackQueue;
1579 dispatch_async(dispatch_get_main_queue(), ^{
1580 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1581 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1583 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1584 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1585 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1586 object:request] autorelease];
1587 operation.completionBlock = ^{
1588 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1589 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1590 dispatch_async(dispatch_get_main_queue(), ^{
1591 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1592 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1597 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1598 [callbackQueue addOperation:operation];
1601 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1602 if (request.isCancelled) {
1603 // Request has been cancelled
1604 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1605 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1606 withObject:request];
1608 NSOperationQueue *callbackQueue;
1609 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1610 if ([operationType isEqualToString:@"move"])
1611 callbackQueue = moveCallbackQueue;
1612 else if ([operationType isEqualToString:@"copy"])
1613 callbackQueue = copyCallbackQueue;
1614 else if ([operationType isEqualToString:@"delete"])
1615 callbackQueue = deleteCallbackQueue;
1616 else if ([operationType isEqualToString:@"upload"])
1617 callbackQueue = uploadCallbackQueue;
1618 else if ([operationType isEqualToString:@"download"])
1619 callbackQueue = downloadCallbackQueue;
1621 dispatch_async(dispatch_get_main_queue(), ^{
1622 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1623 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1625 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1626 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1627 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1628 object:request] autorelease];
1629 operation.completionBlock = ^{
1630 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1631 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1632 dispatch_async(dispatch_get_main_queue(), ^{
1633 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1634 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1639 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1640 [callbackQueue addOperation:operation];
1644 - (void)requestFailed:(ASIPithosRequest *)request {
1645 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1646 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1647 NSLog(@"Request failed: %@", request.url);
1648 if (operation.isCancelled) {
1652 if (request.isCancelled) {
1653 dispatch_async(dispatch_get_main_queue(), ^{
1654 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1655 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1660 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1662 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1663 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1664 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1665 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1667 dispatch_async(dispatch_get_main_queue(), ^{
1668 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1669 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1670 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1671 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1673 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1679 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1680 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1681 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1682 NSLog(@"Download finished: %@", objectRequest.url);
1683 if (operation.isCancelled) {
1684 [self requestFailed:objectRequest];
1685 } else if (objectRequest.responseStatusCode == 200) {
1686 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1687 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1688 NSUInteger totalBytes = activity.totalBytes;
1690 // XXX change contentLength to objectContentLength if it is fixed in the server
1691 if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1692 NSLog(@"Downloaded 0 bytes");
1693 NSFileManager *fileManager = [NSFileManager defaultManager];
1694 if (![fileManager fileExistsAtPath:filePath]) {
1695 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1696 dispatch_async(dispatch_get_main_queue(), ^{
1697 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1698 [alert setMessageText:@"Create File Error"];
1699 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1700 [alert addButtonWithTitle:@"OK"];
1707 NSUInteger currentBytes = [objectRequest objectContentLength];
1708 if (currentBytes == 0)
1709 currentBytes = totalBytes;
1710 dispatch_async(dispatch_get_main_queue(), ^{
1711 [activityFacility endActivity:activity
1712 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1713 totalBytes:totalBytes
1714 currentBytes:currentBytes];
1717 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1718 [self requestFailed:objectRequest];
1723 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1724 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1725 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1726 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1727 if (operation.isCancelled) {
1728 [self requestFailed:objectRequest];
1729 } else if (objectRequest.responseStatusCode == 201) {
1730 NSLog(@"Directory object created: %@", objectRequest.url);
1731 dispatch_async(dispatch_get_main_queue(), ^{
1732 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1733 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1735 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1736 [node forceRefresh];
1738 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1741 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1742 [self forceRefresh:self];
1743 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1744 [self refresh:self];
1746 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1747 [self requestFailed:objectRequest];
1752 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1753 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1754 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1755 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1756 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1757 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1758 NSUInteger totalBytes = activity.totalBytes;
1759 NSUInteger currentBytes = activity.currentBytes;
1760 if (operation.isCancelled) {
1761 [self requestFailed:objectRequest];
1762 } else if (objectRequest.responseStatusCode == 201) {
1763 NSLog(@"Object created: %@", objectRequest.url);
1764 dispatch_async(dispatch_get_main_queue(), ^{
1765 [activityFacility endActivity:activity
1766 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1767 totalBytes:totalBytes
1768 currentBytes:totalBytes];
1770 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1771 [node forceRefresh];
1773 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1776 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1777 [self forceRefresh:self];
1778 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1779 [self refresh:self];
1780 } else if (objectRequest.responseStatusCode == 409) {
1781 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1782 if (iteration == 0) {
1783 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1784 dispatch_async(dispatch_get_main_queue(), ^{
1785 [activityFacility endActivity:activity
1786 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1787 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1788 [alert setMessageText:@"Upload Timeout"];
1789 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1790 [objectRequest.userInfo objectForKey:@"objectName"]]];
1791 [alert addButtonWithTitle:@"OK"];
1797 NSLog(@"object is missing hashes: %@", objectRequest.url);
1798 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1799 withMissingHashes:[objectRequest hashes]];
1800 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1801 if (totalBytes >= [missingBlocks count]*blockSize)
1802 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1803 dispatch_async(dispatch_get_main_queue(), ^{
1804 [activityFacility updateActivity:activity
1805 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1806 totalBytes:totalBytes
1807 currentBytes:currentBytes];
1809 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1810 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1811 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1813 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1814 missingBlockIndex:missingBlockIndex
1815 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1816 newContainerRequest.delegate = self;
1817 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1818 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1819 newContainerRequest.userInfo = objectRequest.userInfo;
1820 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1821 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1822 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1823 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1824 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1825 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1826 [activityFacility updateActivity:activity
1827 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1828 totalBytes:activity.totalBytes
1829 currentBytes:(activity.currentBytes + size)];
1831 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1833 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1834 [self requestFailed:objectRequest];
1839 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1840 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1841 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1842 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1843 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1844 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1845 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1846 if (operation.isCancelled) {
1847 [self requestFailed:containerRequest];
1848 } else if (containerRequest.responseStatusCode == 202) {
1849 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1850 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1851 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1852 if (missingBlockIndex == NSNotFound) {
1853 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1854 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1855 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1856 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1857 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1859 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1860 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1863 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1864 newObjectRequest.delegate = self;
1865 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1866 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1867 newObjectRequest.userInfo = containerRequest.userInfo;
1868 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1869 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1870 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1871 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1872 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1874 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1875 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1876 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1877 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1878 missingBlockIndex:missingBlockIndex
1879 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1880 newContainerRequest.delegate = self;
1881 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1882 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1883 newContainerRequest.userInfo = containerRequest.userInfo;
1884 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1885 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1886 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1887 [activityFacility updateActivity:activity
1888 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1889 totalBytes:activity.totalBytes
1890 currentBytes:(activity.currentBytes + size)];
1892 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1895 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1896 [self requestFailed:containerRequest];
1901 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1902 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1903 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1904 NSLog(@"Move object finished: %@", objectRequest.url);
1905 if (operation.isCancelled) {
1906 [self requestFailed:objectRequest];
1907 } else if (objectRequest.responseStatusCode == 201) {
1908 dispatch_async(dispatch_get_main_queue(), ^{
1909 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1910 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1912 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1913 [node forceRefresh];
1915 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1918 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1919 [self forceRefresh:self];
1920 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1921 [self refresh:self];
1923 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1924 [self requestFailed:objectRequest];
1929 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1930 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1931 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1932 NSLog(@"Copy object finished: %@", objectRequest.url);
1933 if (operation.isCancelled) {
1934 [self requestFailed:objectRequest];
1935 } else if (objectRequest.responseStatusCode == 201) {
1936 dispatch_async(dispatch_get_main_queue(), ^{
1937 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1938 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1940 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1941 [node forceRefresh];
1943 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1946 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1947 [self forceRefresh:self];
1948 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1949 [self refresh:self];
1951 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1952 [self requestFailed:objectRequest];
1957 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1958 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1959 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1960 NSLog(@"Delete object finished: %@", objectRequest.url);
1961 if (operation.isCancelled) {
1962 [self requestFailed:objectRequest];
1963 } else if (objectRequest.responseStatusCode == 204) {
1964 dispatch_async(dispatch_get_main_queue(), ^{
1965 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1966 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1968 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1969 [node forceRefresh];
1971 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1974 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1975 [self forceRefresh:self];
1976 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1977 [self refresh:self];
1979 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1980 [self requestFailed:objectRequest];
1986 #pragma mark NSSplitViewDelegate
1988 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1989 if (splitView == verticalSplitView)
1992 return ([horizontalSplitView bounds].size.height - 108);
1995 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1996 if (splitView == verticalSplitView)
1999 return ([horizontalSplitView bounds].size.height - 108);
2002 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2003 if (splitView == verticalSplitView) {
2004 if (proposedPosition < 120)
2006 else if (proposedPosition > 220)
2009 return proposedPosition;
2011 return ([horizontalSplitView bounds].size.height - 108);
2016 #pragma mark NSOutlineViewDataSource
2018 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2021 if (item == containersNode)
2022 return containersNodeChildren.count;
2023 if (item == sharedNode)
2028 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2030 return (!index ? containersNode : sharedNode);
2031 if (item == sharedNode)
2032 return (!index ? mySharedNode : othersSharedNode);
2033 return [containersNodeChildren objectAtIndex:index];
2036 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2037 if ((item == containersNode) || (item == sharedNode))
2042 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2043 PithosNode *node = (PithosNode *)item;
2047 #pragma mark Drag and Drop destination
2049 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2050 validateDrop:(id<NSDraggingInfo>)info
2051 proposedItem:(id)item
2052 proposedChildIndex:(NSInteger)index {
2053 NSDragOperation result = NSDragOperationNone;
2054 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2056 PithosNode *dropNode = (PithosNode *)item;
2057 if ([dropNode class] != [PithosContainerNode class])
2059 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2060 result = NSDragOperationCopy;
2061 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2062 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
2063 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2064 if (![dropNode isEqualTo:draggedParentNode])
2065 result = NSDragOperationMove;
2066 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2067 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2068 result = NSDragOperationCopy;
2074 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2075 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2076 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2077 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2078 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2079 PithosNode *node = (PithosNode *)item;
2080 NSLog(@"drag in node: %@", node.url);
2081 return [self uploadFiles:filenames toNode:node];
2083 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2084 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2085 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2086 PithosNode *node = (PithosNode *)item;
2087 NSLog(@"drag local node: %@", node.url);
2088 if ([info draggingSourceOperationMask] & NSDragOperationMove)
2089 return [self moveNodes:draggedNodes toNode:node];
2090 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2091 return [self copyNodes:draggedNodes toNode:node];
2098 #pragma mark NSOutlineViewDelegate
2100 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2101 if ((item == containersNode) || (item == sharedNode))
2106 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2107 if ((item == containersNode) || (item == sharedNode))
2112 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2113 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2116 [browser loadColumnZero];
2122 #pragma mark NSMenuDelegate
2124 - (void)menuNeedsUpdate:(NSMenu *)menu {
2125 [menu removeAllItems];
2126 NSMenuItem *menuItem;
2127 NSString *menuItemTitle;
2128 BOOL nodeContextMenu = NO;
2129 PithosNode *menuNode = nil;
2130 NSMutableArray *menuNodes;
2131 if (menu == browserMenu) {
2132 NSInteger column = [browser clickedColumn];
2133 NSInteger row = [browser clickedRow];
2134 if ((column == -1) || (row == -1)) {
2135 // General context menu
2136 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2137 if ([menuNodesIndexPaths count] == 0) {
2138 menuNode = [browser parentForItemsInColumn:0];
2139 } else if (([menuNodesIndexPaths count] != 1) ||
2140 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2141 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2143 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2146 // Node context menu
2147 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2148 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2149 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2150 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2151 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2152 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2155 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2157 nodeContextMenu = YES;
2159 } else if (menu == outlineViewMenu) {
2160 NSInteger row = [outlineView clickedRow];
2162 row = [outlineView selectedRow];
2165 menuNode = [outlineView itemAtRow:row];
2168 if (!nodeContextMenu) {
2169 // General context menu
2170 if (([menuNode class] == [PithosAccountNode class]) ||
2171 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2172 ([menuNode class] == [PithosEmptyNode class]))
2174 BOOL shared = menuNode.shared;
2175 BOOL sharingAccount = (menuNode.sharingAccount != nil);
2177 if (!shared && !sharingAccount) {
2178 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2179 [menuItem setRepresentedObject:menuNode];
2180 [menu addItem:menuItem];
2181 [menu addItem:[NSMenuItem separatorItem]];
2184 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2185 [menu addItem:menuItem];
2186 [menu addItem:[NSMenuItem separatorItem]];
2188 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2189 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2190 [menu addItem:menuItem];
2192 if (!shared && !sharingAccount) {
2193 if (clipboardNodes) {
2194 NSUInteger clipboardNodesCount = [clipboardNodes count];
2195 if (clipboardNodesCount == 0) {
2196 self.clipboardNodes = nil;
2197 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2198 if (clipboardNodesCount == 1)
2199 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2201 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2202 [menu addItem:[NSMenuItem separatorItem]];
2203 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2204 [menuItem setRepresentedObject:menuNode];
2205 [menu addItem:menuItem];
2210 // Node context menu
2211 NSUInteger menuNodesCount = [menuNodes count];
2212 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
2213 BOOL shared = firstMenuNode.shared;
2214 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
2215 // Move to Trash (pithos container only)
2217 if (!shared && !sharingAccount) {
2218 if ([rootNode class] == [PithosContainerNode class]) {
2219 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2220 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
2221 [menuItem setRepresentedObject:menuNodes];
2222 [menu addItem:menuItem];
2224 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2225 [menuItem setRepresentedObject:menuNodes];
2226 [menu addItem:menuItem];
2227 [menu addItem:[NSMenuItem separatorItem]];
2231 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2232 [menu addItem:menuItem];
2233 [menu addItem:[NSMenuItem separatorItem]];
2235 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2236 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2237 [menuItem setRepresentedObject:menuNodes];
2238 [menu addItem:menuItem];
2240 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2241 [menu addItem:[NSMenuItem separatorItem]];
2244 if (!shared && !sharingAccount) {
2245 if (menuNodesCount == 1)
2246 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2248 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2249 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2250 [menuItem setRepresentedObject:menuNodes];
2251 [menu addItem:menuItem];
2254 if ((!shared && !sharingAccount) ||
2255 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2256 if (menuNodesCount == 1)
2257 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2259 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2260 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2261 [menuItem setRepresentedObject:menuNodes];
2262 [menu addItem:menuItem];
2265 if (!shared && !sharingAccount) {
2266 if (menuNodesCount == 1) {
2267 PithosNode *menuNode = [menuNodes objectAtIndex:0];
2268 if (([menuNode class] == [PithosSubdirNode class]) &&
2269 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
2270 if (clipboardNodes) {
2271 NSUInteger clipboardNodesCount = [clipboardNodes count];
2272 if (clipboardNodesCount == 0) {
2273 self.clipboardNodes = nil;
2274 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2275 if (clipboardNodesCount == 1)
2276 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2278 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2279 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2280 [menuItem setRepresentedObject:menuNode];
2281 [menu addItem:menuItem];
2291 #pragma mark Menu Actions
2293 - (void)menuNewFolder:(NSMenuItem *)sender {
2294 PithosNode *node = (PithosNode *)[sender representedObject];
2295 if ([node class] == [PithosContainerNode class]) {
2296 // Operation: Create (upload) a new root application/directory object
2297 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2298 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2299 if (operation.isCancelled) {
2303 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2304 containerName:node.pithosContainer.name
2305 subdirName:@"untitled folder"];
2306 NSString *fileName = [safeObjectName lastPathComponent];
2307 if (operation.isCancelled) {
2311 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2312 containerName:node.pithosContainer.name
2313 objectName:safeObjectName
2315 contentType:@"application/directory"
2317 contentDisposition:nil
2320 isPublic:ASIPithosObjectRequestPublicIgnore
2322 data:[NSData data]];
2323 objectRequest.delegate = self;
2324 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2325 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2326 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2327 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2328 message:messagePrefix];
2329 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2330 fileName, @"fileName",
2331 [NSArray arrayWithObject:node], @"refreshNodes",
2332 [NSNumber numberWithBool:YES], @"refresh",
2333 activity, @"activity",
2334 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2335 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2336 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2337 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2338 [NSNumber numberWithUnsignedInteger:10], @"retries",
2339 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2340 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2341 uploadNetworkQueue, @"networkQueue",
2342 @"upload", @"operationType",
2344 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2347 [uploadQueue addOperation:operation];
2348 } else if (([node class] == [PithosSubdirNode class]) &&
2349 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2350 // Operation: Create (upload) a new aplication/directory object
2351 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2352 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2353 if (operation.isCancelled) {
2357 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2358 containerName:node.pithosContainer.name
2359 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2360 NSString *fileName = [safeObjectName lastPathComponent];
2361 if (operation.isCancelled) {
2365 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2366 containerName:node.pithosContainer.name
2367 objectName:safeObjectName
2369 contentType:@"application/directory"
2371 contentDisposition:nil
2374 isPublic:ASIPithosObjectRequestPublicIgnore
2376 data:[NSData data]];
2377 objectRequest.delegate = self;
2378 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2379 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2380 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2381 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2382 message:messagePrefix];
2383 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2384 fileName, @"fileName",
2385 [NSArray arrayWithObject:node], @"refreshNodes",
2386 [NSNumber numberWithBool:YES], @"refresh",
2387 activity, @"activity",
2388 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2389 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2390 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2391 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2392 [NSNumber numberWithUnsignedInteger:10], @"retries",
2393 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2394 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2395 uploadNetworkQueue, @"networkQueue",
2396 @"upload", @"operationType",
2398 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2401 [uploadQueue addOperation:operation];
2405 - (void)menuGetInfo:(NSMenuItem *)sender {
2406 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2407 [node showPithosNodeInfo:sender];
2411 - (void)menuDelete:(NSMenuItem *)sender {
2412 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2413 if (([node class] == [PithosObjectNode class]) ||
2414 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2415 // Operation: Delete an object or subdir/ node
2416 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2417 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2418 if (operation.isCancelled) {
2422 NSString *fileName = [node.pithosObject.name lastPathComponent];
2423 if ([node.pithosObject.name hasSuffix:@"/"])
2424 fileName = [fileName stringByAppendingString:@"/"];
2425 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2426 containerName:node.pithosContainer.name
2427 objectName:node.pithosObject.name];
2428 if (operation.isCancelled) {
2432 objectRequest.delegate = self;
2433 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2434 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2435 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2436 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2437 message:messagePrefix];
2438 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2439 fileName, @"fileName",
2440 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2441 activity, @"activity",
2442 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2443 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2444 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2445 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2446 [NSNumber numberWithUnsignedInteger:10], @"retries",
2447 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2448 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2449 deleteNetworkQueue, @"networkQueue",
2450 @"delete", @"operationType",
2452 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2455 [deleteQueue addOperation:operation];
2456 } else if ([node class] == [PithosSubdirNode class]) {
2457 // Operation: Delete a subdir node and its descendants
2458 // The resulting ASIPithosObjectRequests are chained through dependencies
2459 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2460 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2461 if (operation.isCancelled) {
2465 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2466 containerName:node.pithosContainer.name
2467 objectName:node.pithosObject.name];
2468 if (!operation.isCancelled && objectRequests) {
2469 ASIPithosObjectRequest *previousObjectRequest = nil;
2470 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2471 if (operation.isCancelled) {
2475 objectRequest.delegate = self;
2476 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2477 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2478 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2479 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2480 message:messagePrefix];
2481 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2482 [NSDictionary dictionaryWithObjectsAndKeys:
2483 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2484 activity, @"activity",
2485 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2486 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2487 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2488 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2489 [NSNumber numberWithUnsignedInteger:10], @"retries",
2490 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2491 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2492 deleteNetworkQueue, @"networkQueue",
2493 @"delete", @"operationType",
2495 if (previousObjectRequest)
2496 [objectRequest addDependency:previousObjectRequest];
2497 previousObjectRequest = objectRequest;
2498 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2503 [deleteQueue addOperation:operation];
2508 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2509 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2510 if (([node class] == [PithosObjectNode class]) ||
2511 (([node class] == [PithosSubdirNode class]) &&
2512 !node.pithosObject.subdir &&
2513 [node.pithosObject.name hasSuffix:@"/"])) {
2514 // Operation: Move to trash an object or subdir/ node
2515 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2516 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2517 if (operation.isCancelled) {
2521 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2522 containerName:@"trash"
2523 objectName:node.pithosObject.name];
2524 if (!operation.isCancelled && safeObjectName) {
2525 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2526 containerName:node.pithosContainer.name
2527 objectName:node.pithosObject.name
2528 destinationContainerName:@"trash"
2529 destinationObjectName:safeObjectName
2531 if (!operation.isCancelled && objectRequest) {
2532 objectRequest.delegate = self;
2533 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2534 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2535 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2536 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2537 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2538 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2539 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2540 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2541 message:messagePrefix];
2542 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2543 [NSDictionary dictionaryWithObjectsAndKeys:
2544 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2545 activity, @"activity",
2546 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2547 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2548 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2549 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2550 [NSNumber numberWithUnsignedInteger:10], @"retries",
2551 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2552 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2553 moveNetworkQueue, @"networkQueue",
2554 @"move", @"operationType",
2556 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2561 [moveQueue addOperation:operation];
2562 } else if ([node class] == [PithosSubdirNode class]) {
2563 // Operation: Move to trash a subdir node and its descendants
2564 // The resulting ASIPithosObjectRequests are chained through dependencies
2565 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2566 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2567 if (operation.isCancelled) {
2571 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2572 containerName:@"trash"
2573 subdirName:node.pithosObject.name];
2574 if (!operation.isCancelled && safeObjectName) {
2575 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2576 containerName:node.pithosContainer.name
2577 objectName:node.pithosObject.name
2578 destinationContainerName:@"trash"
2579 destinationObjectName:safeObjectName
2581 if (!operation.isCancelled && objectRequests) {
2582 ASIPithosObjectRequest *previousObjectRequest = nil;
2583 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2584 if (operation.isCancelled) {
2588 objectRequest.delegate = self;
2589 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2590 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2591 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2592 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2593 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2594 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2595 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2596 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2597 message:messagePrefix];
2598 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2599 [NSDictionary dictionaryWithObjectsAndKeys:
2600 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2601 activity, @"activity",
2602 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2603 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2604 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2605 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2606 [NSNumber numberWithUnsignedInteger:10], @"retries",
2607 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2608 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2609 moveNetworkQueue, @"networkQueue",
2610 @"move", @"operationType",
2612 if (previousObjectRequest)
2613 [objectRequest addDependency:previousObjectRequest];
2614 previousObjectRequest = objectRequest;
2615 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2621 [moveQueue addOperation:operation];
2626 - (void)menuCut:(NSMenuItem *)sender {
2627 self.clipboardNodes = (NSArray *)[sender representedObject];
2628 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2629 self.clipboardCopy = NO;
2632 - (void)menuCopy:(NSMenuItem *)sender {
2633 self.clipboardNodes = (NSArray *)[sender representedObject];
2634 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2635 self.clipboardCopy = YES;
2638 - (void)menuPaste:(NSMenuItem *)sender {
2639 if (!clipboardNodes || ![clipboardNodes count])
2641 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2642 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2643 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2644 self.clipboardNodes = nil;
2645 self.clipboardParentNode = nil;
2646 [self moveNodes:localClipboardNodes toNode:dropNode];
2648 [self copyNodes:localClipboardNodes toNode:dropNode];
2653 #pragma mark PithosActivityFacilityDelegate
2655 - (void)activityUpdate:(NSDictionary *)info {
2656 NSString *message = [info objectForKey:@"message"];
2657 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2658 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2659 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2660 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2661 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2662 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2663 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2664 if (runningActivitiesCount && totalBytes) {
2665 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2666 [activityProgressIndicator startAnimation:self];
2668 [activityProgressIndicator setDoubleValue:1.0];
2669 [activityProgressIndicator stopAnimation:self];
2673 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2674 [activityTextField setStringValue:message];