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 // PithosAccountNode accountNode updates outlineView container nodes
227 [[NSNotificationCenter defaultCenter] addObserver:self
228 selector:@selector(pithosAccountNodeChildrenUpdated:)
229 name:@"PithosNodeChildrenUpdated"
231 // PithosNode updates browser nodes
232 [[NSNotificationCenter defaultCenter] addObserver:self
233 selector:@selector(pithosNodeChildrenUpdated:)
234 name:@"PithosNodeChildrenUpdated"
236 // Request for browser refresh
237 [[NSNotificationCenter defaultCenter] addObserver:self
238 selector:@selector(pithosBrowserRefreshNeeded:)
239 name:@"PithosBrowserRefreshNeeeded"
243 - (void)resetBrowser {
244 @synchronized(self) {
249 [refreshTimer invalidate];
250 [refreshTimer release];
252 [moveNetworkQueue reset];
253 [copyNetworkQueue reset];
254 [deleteNetworkQueue reset];
255 [uploadNetworkQueue reset];
256 [downloadNetworkQueue reset];
258 [moveQueue cancelAllOperations];
259 [moveQueue setSuspended:YES];
260 [copyQueue cancelAllOperations];
261 [copyQueue setSuspended:YES];
262 [deleteQueue cancelAllOperations];
263 [deleteQueue setSuspended:YES];
264 [uploadQueue cancelAllOperations];
265 [uploadQueue setSuspended:YES];
266 [downloadQueue cancelAllOperations];
267 [downloadQueue setSuspended:YES];
269 [moveCallbackQueue cancelAllOperations];
270 [moveCallbackQueue setSuspended:YES];
271 [copyCallbackQueue cancelAllOperations];
272 [copyCallbackQueue setSuspended:YES];
273 [deleteCallbackQueue cancelAllOperations];
274 [deleteCallbackQueue setSuspended:YES];
275 [uploadCallbackQueue cancelAllOperations];
276 [uploadCallbackQueue setSuspended:YES];
277 [downloadCallbackQueue cancelAllOperations];
278 [downloadCallbackQueue setSuspended:YES];
281 [browser loadColumnZero];
282 [containersNodeChildren removeAllObjects];
283 [outlineView reloadData];
284 // Expand the folder outline view
285 [outlineView expandItem:nil expandChildren:YES];
286 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
288 activityFacility.delegate = nil;
289 [activityProgressIndicator setDoubleValue:1.0];
290 [activityProgressIndicator stopAnimation:self];
292 @synchronized(self) {
297 - (void)startBrowser {
298 @synchronized(self) {
303 // In the improbable case of leftover operations
304 [moveNetworkQueue reset];
305 [copyNetworkQueue reset];
306 [deleteNetworkQueue reset];
307 [uploadNetworkQueue reset];
308 [downloadNetworkQueue reset];
309 [moveQueue cancelAllOperations];
310 [copyQueue cancelAllOperations];
311 [deleteQueue cancelAllOperations];
312 [uploadQueue cancelAllOperations];
313 [downloadQueue cancelAllOperations];
314 [moveCallbackQueue cancelAllOperations];
315 [copyCallbackQueue cancelAllOperations];
316 [deleteCallbackQueue cancelAllOperations];
317 [uploadCallbackQueue cancelAllOperations];
318 [downloadCallbackQueue cancelAllOperations];
320 [moveNetworkQueue go];
321 [copyNetworkQueue go];
322 [deleteNetworkQueue go];
323 [uploadNetworkQueue go];
324 [downloadNetworkQueue go];
325 [moveQueue setSuspended:NO];
326 [copyQueue setSuspended:NO];
327 [deleteQueue setSuspended:NO];
328 [uploadQueue setSuspended:NO];
329 [downloadQueue setSuspended:NO];
330 [moveCallbackQueue setSuspended:NO];
331 [copyCallbackQueue setSuspended:NO];
332 [deleteCallbackQueue setSuspended:NO];
333 [uploadCallbackQueue setSuspended:NO];
334 [downloadCallbackQueue setSuspended:NO];
336 accountNode.pithos = pithos;
337 [accountNode forceRefresh];
338 mySharedNode.pithos = pithos;
339 [mySharedNode forceRefresh];
340 othersSharedNode.pithos = pithos;
341 [othersSharedNode forceRefresh];
343 // [activityFacility reset];
344 activityFacility.delegate = self;
346 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30
348 selector:@selector(forceRefresh:)
350 repeats:YES] retain];
351 @synchronized(self) {
356 - (BOOL)operationsPending {
357 return ([moveNetworkQueue operationCount] ||
358 [copyNetworkQueue operationCount] ||
359 [deleteNetworkQueue operationCount] ||
360 [uploadNetworkQueue operationCount] ||
361 [downloadNetworkQueue operationCount] ||
362 [moveQueue operationCount] ||
363 [copyQueue operationCount] ||
364 [deleteQueue operationCount] ||
365 [uploadQueue operationCount] ||
366 [downloadQueue operationCount] ||
367 [moveCallbackQueue operationCount] ||
368 [copyCallbackQueue operationCount] ||
369 [deleteCallbackQueue operationCount] ||
370 [uploadCallbackQueue operationCount] ||
371 [downloadCallbackQueue operationCount]);
375 [[NSNotificationCenter defaultCenter] removeObserver:self];
379 [deleteQueue release];
380 [uploadQueue release];
381 [downloadQueue release];
382 [moveNetworkQueue release];
383 [copyNetworkQueue release];
384 [deleteNetworkQueue release];
385 [uploadNetworkQueue release];
386 [downloadNetworkQueue release];
387 [clipboardParentNode release];
388 [clipboardNodes release];
389 [draggedParentNode release];
390 [draggedNodes release];
391 [sharedPreviewController release];
392 [othersSharedNode release];
393 [mySharedNode release];
394 [sharedNode release];
395 [containersNodeChildren release];
396 [containersNode release];
397 [accountNode release];
403 - (void)setPithos:(ASIPithos *)aPithos {
405 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
406 ![aPithos.authToken isEqualToString:pithos.authToken] ||
407 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
408 ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
411 pithos = [aPithos retain];
421 #pragma mark Observers
423 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
424 PithosNode *node = (PithosNode *)[notification object];
425 if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
427 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
428 NSInteger lastColumn = [browser lastColumn];
429 for (NSInteger column = lastColumn; column >= 0; column--) {
430 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
431 [browser reloadColumn:column];
437 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
438 BOOL containerPithosFound = NO;
439 BOOL containerTrashFound = NO;
440 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
441 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
442 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
443 [removedContainersNodeChildren addIndex:i];
445 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
446 for (PithosContainerNode *containerNode in accountNode.children) {
447 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
448 if (![containersNodeChildren containsObject:containerNode])
449 [containersNodeChildren insertObject:containerNode atIndex:0];
450 containerPithosFound = YES;
451 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
452 NSUInteger insertIndex = 1;
453 if (!containerPithosFound)
455 if (![containersNodeChildren containsObject:containerNode])
456 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
457 containerTrashFound = YES;
458 } else if (![containersNodeChildren containsObject:containerNode]) {
459 [containersNodeChildren addObject:containerNode];
462 BOOL refreshAccountNode = NO;
463 if (!containerPithosFound) {
464 // Create pithos node
465 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
466 containerName:@"pithos"];
467 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
469 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
470 if ([containerRequest error]) {
471 dispatch_async(dispatch_get_main_queue(), ^{
472 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
475 refreshAccountNode = YES;
478 if (!containerTrashFound) {
480 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
481 containerName:@"trash"];
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;
494 if (refreshAccountNode)
495 [accountNode refresh];
497 [outlineView reloadData];
499 // Expand the folder outline view
500 [outlineView expandItem:nil expandChildren:YES];
502 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
503 rootNode = [containersNodeChildren objectAtIndex:0];
504 [browser loadColumnZero];
511 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
518 - (IBAction)forceRefresh:(id)sender {
520 [accountNode forceRefresh];
521 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
522 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
523 node.forcedRefresh = YES;
524 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
526 [browser validateVisibleColumns];
529 - (IBAction)refresh:(id)sender {
530 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
531 [self forceRefresh:sender];
534 [accountNode refresh];
535 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
536 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
538 [browser validateVisibleColumns];
543 #pragma mark NSBrowserDelegate
545 - (id)rootItemForBrowser:(NSBrowser *)browser {
549 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
550 PithosNode *node = (PithosNode *)item;
551 return node.children.count;
554 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
555 PithosNode *node = (PithosNode *)item;
556 return [node.children objectAtIndex:index];
559 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
560 PithosNode *node = (PithosNode *)item;
561 return node.isLeafItem;
564 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
565 PithosNode *node = (PithosNode *)item;
569 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
570 if (sharedPreviewController == nil)
571 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
572 return sharedPreviewController;
575 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
576 // if (!forUserResize) {
577 // id item = [browser parentForItemsInColumn:columnIndex];
578 // if ([self browser:browser isLeafItem:item]) {
579 // suggestedWidth = 200;
582 // return suggestedWidth;
585 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
591 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
592 PithosNode *node = (PithosNode *)item;
593 if (node.shared || node.sharingAccount ||
594 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
599 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
600 PithosNode *node = (PithosNode *)item;
601 NSString *newName = (NSString *)object;
602 NSUInteger newNameLength = [newName length];
603 NSRange firstSlashRange = [newName rangeOfString:@"/"];
604 if ((newNameLength == 0) ||
605 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
606 ([newName isEqualToString:node.displayName])) {
609 if (([node class] == [PithosObjectNode class]) ||
610 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
611 // Operation: Rename (move) an object or subdir/ node
612 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
613 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
614 if (operation.isCancelled) {
618 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
619 if ([newName hasSuffix:@"/"])
620 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
621 NSError *error = nil;
623 if ([PithosUtilities objectExistsAtPithos:pithos
624 containerName:node.pithosContainer.name
625 objectName:destinationObjectName
627 isDirectory:&isDirectory
628 sharingAccount:nil]) {
629 dispatch_async(dispatch_get_main_queue(), ^{
630 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
631 [alert setMessageText:@"Name Taken"];
632 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
633 [alert addButtonWithTitle:@"OK"];
642 if (operation.isCancelled) {
646 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
647 containerName:node.pithosContainer.name
648 objectName:node.pithosObject.name
649 destinationContainerName:node.pithosContainer.name
650 destinationObjectName:destinationObjectName
652 if (!operation.isCancelled && objectRequest) {
653 objectRequest.delegate = self;
654 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
655 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
656 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
657 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
658 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
659 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
660 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
661 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
662 message:messagePrefix];
663 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
664 [NSDictionary dictionaryWithObjectsAndKeys:
665 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
666 [NSNumber numberWithBool:YES], @"refresh",
667 activity, @"activity",
668 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
669 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
670 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
671 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
672 [NSNumber numberWithUnsignedInteger:10], @"retries",
673 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
674 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
675 moveNetworkQueue, @"networkQueue",
676 @"move", @"operationType",
678 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
682 [moveQueue addOperation:operation];
683 } else if ([node class] == [PithosSubdirNode class]) {
684 if (firstSlashRange.length == 1)
686 // Operation: Rename (move) a subdir node and its descendants
687 // The resulting ASIPithosObjectRequests are chained through dependencies
688 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
689 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
690 if (operation.isCancelled) {
694 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
695 NSError *error = nil;
697 if ([PithosUtilities objectExistsAtPithos:pithos
698 containerName:node.pithosContainer.name
699 objectName:destinationObjectName
701 isDirectory:&isDirectory
702 sharingAccount:nil]) {
703 dispatch_async(dispatch_get_main_queue(), ^{
704 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
705 [alert setMessageText:@"Name Taken"];
706 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
707 [alert addButtonWithTitle:@"OK"];
716 if (operation.isCancelled) {
720 if (node.pithosObject.subdir)
721 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
722 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
723 containerName:node.pithosContainer.name
724 objectName:node.pithosObject.name
725 destinationContainerName:node.pithosContainer.name
726 destinationObjectName:destinationObjectName
728 if (!operation.isCancelled && objectRequests) {
729 ASIPithosObjectRequest *previousObjectRequest = nil;
730 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
731 if (operation.isCancelled) {
735 objectRequest.delegate = self;
736 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
737 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
738 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
739 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
740 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
741 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
742 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
743 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
744 message:messagePrefix];
745 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
746 [NSDictionary dictionaryWithObjectsAndKeys:
747 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
748 [NSNumber numberWithBool:YES], @"refresh",
749 activity, @"activity",
750 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
751 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
752 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
753 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
754 [NSNumber numberWithUnsignedInteger:10], @"retries",
755 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
756 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
757 moveNetworkQueue, @"networkQueue",
758 @"move", @"operationType",
760 if (previousObjectRequest)
761 [objectRequest addDependency:previousObjectRequest];
762 previousObjectRequest = objectRequest;
763 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
768 [moveQueue addOperation:operation];
772 #pragma mark Drag and Drop source
774 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
775 withEvent:(NSEvent *)event {
776 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
777 __block BOOL result = YES;
778 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
779 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
780 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
788 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
789 toPasteboard:(NSPasteboard *)pasteboard {
790 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
791 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
792 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
793 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
794 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
795 [propertyList addObject:[node.pithosObject.name pathExtension]];
796 [nodes addObject:node];
799 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
800 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
801 self.draggedNodes = nodes;
802 self.draggedParentNode = [browser parentForItemsInColumn:column];
806 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
807 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
808 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
809 for (PithosNode *node in draggedNodes) {
810 [names addObject:node.displayName];
811 // If the node is a subdir ask if the whole tree should be downloaded
812 if ([node class] == [PithosSubdirNode class]) {
813 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
814 [alert setMessageText:@"Download directory"];
815 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
816 [alert addButtonWithTitle:@"OK"];
817 [alert addButtonWithTitle:@"Cancel"];
818 NSInteger choice = [alert runModal];
819 if (choice == NSAlertFirstButtonReturn)
820 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil];
822 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil];
828 #pragma mark Drag and Drop destination
830 - (NSDragOperation)browser:aBrowser
831 validateDrop:(id<NSDraggingInfo>)info
832 proposedRow:(NSInteger *)row
833 column:(NSInteger *)column
834 dropOperation:(NSBrowserDropOperation *)dropOperation {
835 NSDragOperation result = NSDragOperationNone;
836 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
837 // For a drop above, the drop is redirected to the parent item
838 if (*dropOperation == NSBrowserDropAbove)
840 // Only allow dropping in folders
842 PithosNode *dropNode;
844 // Check if the node is not a folder and if so redirect to the parent item
845 dropNode = [browser itemAtRow:*row inColumn:*column];
846 if ([dropNode class] == [PithosObjectNode class])
850 dropNode = [browser parentForItemsInColumn:*column];
852 if (!dropNode.shared &&
853 (!dropNode.sharingAccount ||
854 ([dropNode class] == [PithosSubdirNode class]) ||
855 ([dropNode class] == [PithosContainerNode class]))) {
856 *dropOperation = NSBrowserDropOn;
857 result = NSDragOperationCopy;
860 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
861 // For a drop above, the drop is redirected to the parent item
862 if (*dropOperation == NSBrowserDropAbove)
864 // Only allow dropping in folders
866 PithosNode *dropNode;
868 // Check if the node is not a folder and if so redirect to the parent item
869 dropNode = [browser itemAtRow:*row inColumn:*column];
870 if ([dropNode class] == [PithosObjectNode class])
874 dropNode = [browser parentForItemsInColumn:*column];
876 if (!dropNode.shared && !dropNode.sharingAccount) {
877 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
878 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
879 if ((([dropNode class] == [PithosContainerNode class]) ||
880 dropNode.pithosObject.subdir ||
881 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
882 ![dropNode isEqualTo:draggedParentNode]) {
883 // ![dropNode isEqualTo:draggedParentNode] &&
884 // ![draggedNodes containsObject:dropNode]) {
885 result = NSDragOperationMove;
887 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
888 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
889 if (([dropNode class] == [PithosContainerNode class]) ||
890 dropNode.pithosObject.subdir ||
891 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
892 result = NSDragOperationCopy;
901 - (BOOL)browser:(NSBrowser *)aBrowser
902 acceptDrop:(id<NSDraggingInfo>)info
904 column:(NSInteger)column
905 dropOperation:(NSBrowserDropOperation)dropOperation {
906 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
907 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
908 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
909 if ((column != -1) && (filenames != nil)) {
912 node = [browser itemAtRow:row inColumn:column];
914 node = [browser parentForItemsInColumn:column];
915 NSLog(@"drag in node: %@", node.url);
916 return [self uploadFiles:filenames toNode:node];
918 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
919 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
920 if ((column != -1) && (draggedNodes != nil)) {
923 node = [browser itemAtRow:row inColumn:column];
925 node = [browser parentForItemsInColumn:column];
926 NSLog(@"drag local node: %@", node.url);
927 if ([info draggingSourceOperationMask] & NSDragOperationMove)
928 return [self moveNodes:draggedNodes toNode:node];
929 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
930 return [self copyNodes:draggedNodes toNode:node];
937 #pragma mark Drag and Drop methods
939 - (void)downloadNode:(PithosNode *)node
940 toDirectory:(NSString *)dirPath
941 withNewFileName:(NSString *)newFileName
942 version:(NSString *)version {
943 if ([node class] == [PithosSubdirNode class]) {
944 // XXX newFilename and version are ignored in the case of a subdir node for now
945 // Operation: Download a subdir node and its descendants
946 // The resulting ASIPithosObjectRequests are chained through dependencies
947 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
948 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
949 if (operation.isCancelled) {
953 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
954 containerName:node.pithosContainer.name
955 objectName:node.pithosObject.name
958 sharingAccount:node.sharingAccount];
959 if (!operation.isCancelled && objectRequests) {
960 ASIPithosObjectRequest *previousObjectRequest = nil;
961 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
962 if (operation.isCancelled) {
966 objectRequest.delegate = self;
967 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
968 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
969 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
970 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
971 message:[messagePrefix stringByAppendingString:@" (0%)"]
972 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
974 dispatch_async(dispatch_get_main_queue(), ^{
975 [activityFacility updateActivity:activity withMessage:activity.message];
977 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
978 [NSDictionary dictionaryWithObjectsAndKeys:
979 activity, @"activity",
980 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
981 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
982 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
983 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
984 [NSNumber numberWithUnsignedInteger:10], @"retries",
985 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
986 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
987 downloadNetworkQueue, @"networkQueue",
988 @"download", @"operationType",
990 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
991 [activityFacility updateActivity:activity
992 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
993 totalBytes:activity.totalBytes
994 currentBytes:(activity.currentBytes + size)];
996 if (previousObjectRequest)
997 [objectRequest addDependency:previousObjectRequest];
998 previousObjectRequest = objectRequest;
999 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1004 [downloadQueue addOperation:operation];
1005 } else if ([node class] == [PithosObjectNode class]) {
1006 // Operation: Download an object node
1007 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1008 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1009 if (operation.isCancelled) {
1013 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
1014 containerName:node.pithosContainer.name
1015 objectName:node.pithosObject.name
1018 withNewFileName:newFileName
1019 checkIfExists:(version ? NO : YES)
1020 sharingAccount:node.sharingAccount];
1021 if (!operation.isCancelled && objectRequest) {
1022 objectRequest.delegate = self;
1023 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1024 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1025 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1026 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1027 message:[messagePrefix stringByAppendingString:@" (0%)"]
1028 totalBytes:node.pithosObject.bytes
1030 dispatch_async(dispatch_get_main_queue(), ^{
1031 [activityFacility updateActivity:activity withMessage:activity.message];
1033 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1034 [NSDictionary dictionaryWithObjectsAndKeys:
1035 activity, @"activity",
1036 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1037 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1038 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1039 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1040 [NSNumber numberWithUnsignedInteger:10], @"retries",
1041 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1042 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1043 downloadNetworkQueue, @"networkQueue",
1044 @"download", @"operationType",
1046 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1047 [activityFacility updateActivity:activity
1048 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1049 totalBytes:activity.totalBytes
1050 currentBytes:(activity.currentBytes + size)];
1052 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1056 [downloadQueue addOperation:operation];
1060 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1061 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1063 NSFileManager *fileManager = [NSFileManager defaultManager];
1064 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1065 NSString *objectNamePrefix;
1066 if ([destinationNode class] == [PithosSubdirNode class])
1067 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1069 objectNamePrefix = [NSString string];
1070 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1071 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1072 containerName:containerName];
1073 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1075 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1076 if ([containerRequest error]) {
1077 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1079 } else if (containerRequest.responseStatusCode != 204) {
1080 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1083 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1084 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1086 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1087 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1089 for (NSString *filePath in filenames) {
1091 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1094 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1095 // Operation: Upload a local file
1096 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1097 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1098 if (operation.isCancelled) {
1102 NSError *error = nil;
1103 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1104 if (contentType == nil)
1105 contentType = @"application/octet-stream";
1107 NSLog(@"contentType detection error: %@", error);
1108 NSArray *hashes = nil;
1109 if (operation.isCancelled) {
1113 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1114 containerName:containerName
1115 objectName:objectName
1116 contentType:contentType
1122 sharingAccount:destinationNode.sharingAccount];
1123 if (!operation.isCancelled && objectRequest) {
1124 objectRequest.delegate = self;
1125 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1126 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1127 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1128 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1129 message:[messagePrefix stringByAppendingString:@" (0%)"]
1130 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1132 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1133 [NSDictionary dictionaryWithObjectsAndKeys:
1134 containerName, @"containerName",
1135 objectName, @"objectName",
1136 contentType, @"contentType",
1137 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1138 blockHash, @"blockHash",
1139 filePath, @"filePath",
1141 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1142 [NSNumber numberWithBool:YES], @"refresh",
1143 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1144 activity, @"activity",
1145 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1146 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1147 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1148 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1149 [NSNumber numberWithUnsignedInteger:10], @"retries",
1150 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1151 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1152 uploadNetworkQueue, @"networkQueue",
1153 @"upload", @"operationType",
1155 if (destinationNode.sharingAccount)
1156 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1157 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1161 [uploadQueue addOperation:operation];
1163 // Upload directory, confirm first
1164 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1165 [alert setMessageText:@"Upload directory"];
1166 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1167 [alert addButtonWithTitle:@"OK"];
1168 [alert addButtonWithTitle:@"Cancel"];
1169 NSInteger choice = [alert runModal];
1170 if (choice == NSAlertFirstButtonReturn) {
1171 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1172 // Operation: Upload a local directory and its descendants
1173 // The resulting ASIPithosObjectRequests are chained through dependencies
1174 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1175 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1176 if (operation.isCancelled) {
1180 NSMutableArray *objectNames = nil;
1181 NSMutableArray *contentTypes = nil;
1182 NSMutableArray *filePaths = nil;
1183 NSMutableArray *hashesArrays = nil;
1184 NSMutableArray *directoryObjectRequests = nil;
1185 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1186 containerName:containerName
1187 objectName:objectName
1190 forDirectory:filePath
1192 objectNames:&objectNames
1193 contentTypes:&contentTypes
1194 filePaths:&filePaths
1195 hashesArrays:&hashesArrays
1196 directoryObjectRequests:&directoryObjectRequests
1197 sharingAccount:destinationNode.sharingAccount];
1198 if (operation.isCancelled) {
1202 ASIPithosObjectRequest *previousObjectRequest = nil;
1203 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1204 if (operation.isCancelled) {
1208 objectRequest.delegate = self;
1209 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1210 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1211 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1212 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1213 message:messagePrefix];
1214 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1215 [NSDictionary dictionaryWithObjectsAndKeys:
1216 [NSNumber numberWithBool:YES], @"refresh",
1217 activity, @"activity",
1218 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1219 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1220 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1221 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1222 [NSNumber numberWithUnsignedInteger:10], @"retries",
1223 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1224 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1225 uploadNetworkQueue, @"networkQueue",
1226 @"upload", @"operationType",
1228 if (previousObjectRequest)
1229 [objectRequest addDependency:previousObjectRequest];
1230 previousObjectRequest = objectRequest;
1231 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1233 if (!operation.isCancelled && objectRequests) {
1234 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1235 if (operation.isCancelled) {
1239 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1240 objectRequest.delegate = self;
1241 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1242 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1243 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1244 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1245 message:[messagePrefix stringByAppendingString:@" (0%)"]
1246 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1248 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1249 [NSDictionary dictionaryWithObjectsAndKeys:
1250 containerName, @"containerName",
1251 [objectNames objectAtIndex:i], @"objectName",
1252 [contentTypes objectAtIndex:i], @"contentType",
1253 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1254 blockHash, @"blockHash",
1255 [filePaths objectAtIndex:i], @"filePath",
1256 [hashesArrays objectAtIndex:i], @"hashes",
1257 [NSNumber numberWithBool:YES], @"refresh",
1258 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1259 activity, @"activity",
1260 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1261 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1262 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1263 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1264 [NSNumber numberWithUnsignedInteger:10], @"retries",
1265 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1266 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1267 uploadNetworkQueue, @"networkQueue",
1268 @"upload", @"operationType",
1270 if (destinationNode.sharingAccount)
1271 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1272 if (previousObjectRequest)
1273 [objectRequest addDependency:previousObjectRequest];
1274 previousObjectRequest = objectRequest;
1275 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1280 [uploadQueue addOperation:operation];
1288 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1289 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1290 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1292 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1293 NSString *objectNamePrefix;
1294 if ([destinationNode class] == [PithosSubdirNode class])
1295 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1297 objectNamePrefix = [NSString string];
1299 for (PithosNode *node in nodes) {
1300 if (([node class] == [PithosObjectNode class]) ||
1301 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1302 // Operation: Move an object or subdir/ node
1303 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1304 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1305 if (operation.isCancelled) {
1309 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1310 if ([node.pithosObject.name hasSuffix:@"/"])
1311 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1312 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1313 containerName:node.pithosContainer.name
1314 objectName:node.pithosObject.name
1315 destinationContainerName:containerName
1316 destinationObjectName:destinationObjectName
1318 if (!operation.isCancelled && objectRequest) {
1319 objectRequest.delegate = self;
1320 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1321 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1322 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1323 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1324 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1325 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1326 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1327 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1328 message:messagePrefix];
1329 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1330 [NSDictionary dictionaryWithObjectsAndKeys:
1331 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1332 activity, @"activity",
1333 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1334 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1335 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1336 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1337 [NSNumber numberWithUnsignedInteger:10], @"retries",
1338 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1339 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1340 moveNetworkQueue, @"networkQueue",
1341 @"move", @"operationType",
1343 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1347 [moveQueue addOperation:operation];
1348 } else if ([node class] == [PithosSubdirNode class]) {
1349 // Operation: Move a subdir node and its descendants
1350 // The resulting ASIPithosObjectRequests are chained through dependencies
1351 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1352 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1353 if (operation.isCancelled) {
1357 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1358 if (node.pithosObject.subdir)
1359 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1360 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1361 containerName:node.pithosContainer.name
1362 objectName:node.pithosObject.name
1363 destinationContainerName:containerName
1364 destinationObjectName:destinationObjectName
1366 if (!operation.isCancelled && objectRequests) {
1367 ASIPithosObjectRequest *previousObjectRequest = nil;
1368 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1369 if (operation.isCancelled) {
1373 objectRequest.delegate = self;
1374 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1375 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1376 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1377 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1378 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1379 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1380 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1381 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1382 message:messagePrefix];
1383 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1384 [NSDictionary dictionaryWithObjectsAndKeys:
1385 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1386 [NSNumber numberWithBool:YES], @"refresh",
1387 activity, @"activity",
1388 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1389 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1390 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1391 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1392 [NSNumber numberWithUnsignedInteger:10], @"retries",
1393 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1394 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1395 moveNetworkQueue, @"networkQueue",
1396 @"move", @"operationType",
1398 if (previousObjectRequest)
1399 [objectRequest addDependency:previousObjectRequest];
1400 previousObjectRequest = objectRequest;
1401 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1406 [moveQueue addOperation:operation];
1412 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1413 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1414 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1416 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1417 NSString *objectNamePrefix;
1418 if ([destinationNode class] == [PithosSubdirNode class])
1419 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1421 objectNamePrefix = [NSString string];
1423 for (PithosNode *node in nodes) {
1424 if (([node class] == [PithosObjectNode class]) ||
1425 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1426 // Operation: Copy an object or subdir/ node
1427 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1428 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1429 if (operation.isCancelled) {
1433 NSString *destinationObjectName;
1434 if (![destinationNode isEqualTo:node.parent]) {
1435 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1436 if ([node.pithosObject.name hasSuffix:@"/"])
1437 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1439 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1440 containerName:containerName
1441 objectName:node.pithosObject.name];
1443 if (operation.isCancelled) {
1447 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos
1448 containerName:node.pithosContainer.name
1449 objectName:node.pithosObject.name
1450 destinationContainerName:containerName
1451 destinationObjectName:destinationObjectName
1453 sharingAccount:node.sharingAccount];
1454 if (!operation.isCancelled && objectRequest) {
1455 objectRequest.delegate = self;
1456 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1457 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1458 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1459 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1460 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1461 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1462 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1463 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1464 message:messagePrefix];
1465 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1466 [NSDictionary dictionaryWithObjectsAndKeys:
1467 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1468 activity, @"activity",
1469 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1470 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1471 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1472 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1473 [NSNumber numberWithUnsignedInteger:10], @"retries",
1474 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1475 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1476 copyNetworkQueue, @"networkQueue",
1477 @"copy", @"operationType",
1479 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1483 [copyQueue addOperation:operation];
1484 } else if ([node class] == [PithosSubdirNode class]) {
1485 // Operation: Copy a subdir node and its descendants
1486 // The resulting ASIPithosObjectRequests are chained through dependencies
1487 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1488 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1489 if (operation.isCancelled) {
1493 NSString *destinationObjectName;
1494 if (![destinationNode isEqualTo:node.parent]) {
1495 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1496 if (node.pithosObject.subdir)
1497 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1499 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1500 containerName:containerName
1501 subdirName:node.pithosObject.name];
1503 if (operation.isCancelled) {
1507 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos
1508 containerName:node.pithosContainer.name
1509 objectName:node.pithosObject.name
1510 destinationContainerName:containerName
1511 destinationObjectName:destinationObjectName
1513 sharingAccount:node.sharingAccount];
1514 if (!operation.isCancelled && objectRequests) {
1515 ASIPithosObjectRequest *previousObjectRequest = nil;
1516 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1517 if (operation.isCancelled) {
1521 objectRequest.delegate = self;
1522 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1523 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1524 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1525 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1526 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1527 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1528 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1529 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1530 message:messagePrefix];
1531 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1532 [NSDictionary dictionaryWithObjectsAndKeys:
1533 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1534 activity, @"activity",
1535 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1536 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1537 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1538 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1539 [NSNumber numberWithUnsignedInteger:10], @"retries",
1540 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1541 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1542 copyNetworkQueue, @"networkQueue",
1543 @"copy", @"operationType",
1545 if (previousObjectRequest)
1546 [objectRequest addDependency:previousObjectRequest];
1547 previousObjectRequest = objectRequest;
1548 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1553 [copyQueue addOperation:operation];
1560 #pragma mark ASIHTTPRequestDelegate
1562 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1563 NSOperationQueue *callbackQueue;
1564 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1565 if ([operationType isEqualToString:@"move"])
1566 callbackQueue = moveCallbackQueue;
1567 else if ([operationType isEqualToString:@"copy"])
1568 callbackQueue = copyCallbackQueue;
1569 else if ([operationType isEqualToString:@"delete"])
1570 callbackQueue = deleteCallbackQueue;
1571 else if ([operationType isEqualToString:@"upload"])
1572 callbackQueue = uploadCallbackQueue;
1573 else if ([operationType isEqualToString:@"download"])
1574 callbackQueue = downloadCallbackQueue;
1576 dispatch_async(dispatch_get_main_queue(), ^{
1577 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1578 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1582 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1583 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1584 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1585 object:request] autorelease];
1586 operation.completionBlock = ^{
1587 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1588 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1589 dispatch_async(dispatch_get_main_queue(), ^{
1590 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1591 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1596 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1597 [callbackQueue addOperation:operation];
1600 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1601 if (request.isCancelled) {
1602 // Request has been cancelled
1603 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1604 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1605 withObject:request];
1607 NSOperationQueue *callbackQueue;
1608 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1609 if ([operationType isEqualToString:@"move"])
1610 callbackQueue = moveCallbackQueue;
1611 else if ([operationType isEqualToString:@"copy"])
1612 callbackQueue = copyCallbackQueue;
1613 else if ([operationType isEqualToString:@"delete"])
1614 callbackQueue = deleteCallbackQueue;
1615 else if ([operationType isEqualToString:@"upload"])
1616 callbackQueue = uploadCallbackQueue;
1617 else if ([operationType isEqualToString:@"download"])
1618 callbackQueue = downloadCallbackQueue;
1620 dispatch_async(dispatch_get_main_queue(), ^{
1621 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1622 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1626 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1627 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1628 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1629 object:request] autorelease];
1630 operation.completionBlock = ^{
1631 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1632 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1633 dispatch_async(dispatch_get_main_queue(), ^{
1634 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1635 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1640 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1641 [callbackQueue addOperation:operation];
1645 - (void)requestFailed:(ASIPithosRequest *)request {
1646 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1647 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1648 NSLog(@"Request failed: %@", request.url);
1649 if (operation.isCancelled) {
1653 if (request.isCancelled) {
1654 dispatch_async(dispatch_get_main_queue(), ^{
1655 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1656 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1661 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1663 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1664 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1665 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1666 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1668 dispatch_async(dispatch_get_main_queue(), ^{
1669 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1670 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1671 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1672 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1674 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1680 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1681 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1682 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1683 NSLog(@"Download finished: %@", objectRequest.url);
1684 if (operation.isCancelled) {
1685 [self requestFailed:objectRequest];
1686 } else if (objectRequest.responseStatusCode == 200) {
1687 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1688 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1689 NSUInteger totalBytes = activity.totalBytes;
1691 // XXX change contentLength to objectContentLength if it is fixed in the server
1692 if ([objectRequest contentLength] == 0) {
1693 // The check above was:
1694 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1695 // I checked for directory content types in order not to create a file in place of a directory,
1696 // but this callback method is not called in the case of a directory download.
1697 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1698 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1699 NSLog(@"Downloaded 0 bytes");
1700 NSFileManager *fileManager = [NSFileManager defaultManager];
1701 if (![fileManager fileExistsAtPath:filePath]) {
1702 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1703 dispatch_async(dispatch_get_main_queue(), ^{
1704 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1705 [alert setMessageText:@"Create File Error"];
1706 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1707 [alert addButtonWithTitle:@"OK"];
1714 NSUInteger currentBytes = [objectRequest objectContentLength];
1715 if (currentBytes == 0)
1716 currentBytes = totalBytes;
1717 dispatch_async(dispatch_get_main_queue(), ^{
1718 [activityFacility endActivity:activity
1719 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1720 totalBytes:totalBytes
1721 currentBytes:currentBytes];
1724 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1725 [self requestFailed:objectRequest];
1730 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1731 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1732 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1733 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1734 if (operation.isCancelled) {
1735 [self requestFailed:objectRequest];
1736 } else if (objectRequest.responseStatusCode == 201) {
1737 NSLog(@"Directory object created: %@", objectRequest.url);
1738 dispatch_async(dispatch_get_main_queue(), ^{
1739 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1740 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1742 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1743 [node forceRefresh];
1745 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1748 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1749 [self forceRefresh:self];
1750 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1751 [self refresh:self];
1753 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1754 [self requestFailed:objectRequest];
1759 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1760 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1761 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1762 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1763 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1764 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1765 NSUInteger totalBytes = activity.totalBytes;
1766 NSUInteger currentBytes = activity.currentBytes;
1767 if (operation.isCancelled) {
1768 [self requestFailed:objectRequest];
1769 } else if (objectRequest.responseStatusCode == 201) {
1770 NSLog(@"Object created: %@", objectRequest.url);
1771 dispatch_async(dispatch_get_main_queue(), ^{
1772 [activityFacility endActivity:activity
1773 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1774 totalBytes:totalBytes
1775 currentBytes:totalBytes];
1777 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1778 [node forceRefresh];
1780 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1783 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1784 [self forceRefresh:self];
1785 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1786 [self refresh:self];
1787 } else if (objectRequest.responseStatusCode == 409) {
1788 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1789 if (iteration == 0) {
1790 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1791 dispatch_async(dispatch_get_main_queue(), ^{
1792 [activityFacility endActivity:activity
1793 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1794 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1795 [alert setMessageText:@"Upload Timeout"];
1796 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1797 [objectRequest.userInfo objectForKey:@"objectName"]]];
1798 [alert addButtonWithTitle:@"OK"];
1804 NSLog(@"object is missing hashes: %@", objectRequest.url);
1805 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1806 withMissingHashes:[objectRequest hashes]];
1807 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1808 if (totalBytes >= [missingBlocks count]*blockSize)
1809 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1810 dispatch_async(dispatch_get_main_queue(), ^{
1811 [activityFacility updateActivity:activity
1812 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1813 totalBytes:totalBytes
1814 currentBytes:currentBytes];
1816 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1817 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1818 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1820 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1821 missingBlockIndex:missingBlockIndex
1822 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1823 newContainerRequest.delegate = self;
1824 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1825 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1826 newContainerRequest.userInfo = objectRequest.userInfo;
1827 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1828 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1829 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1830 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1831 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1832 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1833 [activityFacility updateActivity:activity
1834 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1835 totalBytes:activity.totalBytes
1836 currentBytes:(activity.currentBytes + size)];
1838 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1840 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1841 [self requestFailed:objectRequest];
1846 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1847 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1848 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1849 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1850 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1851 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1852 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1853 if (operation.isCancelled) {
1854 [self requestFailed:containerRequest];
1855 } else if (containerRequest.responseStatusCode == 202) {
1856 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1857 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1858 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1859 if (missingBlockIndex == NSNotFound) {
1860 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1861 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1862 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1863 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1864 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1866 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1867 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1870 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1871 newObjectRequest.delegate = self;
1872 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1873 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1874 newObjectRequest.userInfo = containerRequest.userInfo;
1875 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1876 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1877 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1878 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1879 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1881 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1882 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1883 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1884 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1885 missingBlockIndex:missingBlockIndex
1886 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1887 newContainerRequest.delegate = self;
1888 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1889 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1890 newContainerRequest.userInfo = containerRequest.userInfo;
1891 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1892 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1893 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1894 [activityFacility updateActivity:activity
1895 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1896 totalBytes:activity.totalBytes
1897 currentBytes:(activity.currentBytes + size)];
1899 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1902 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1903 [self requestFailed:containerRequest];
1908 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1909 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1910 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1911 NSLog(@"Move object finished: %@", objectRequest.url);
1912 if (operation.isCancelled) {
1913 [self requestFailed:objectRequest];
1914 } else if (objectRequest.responseStatusCode == 201) {
1915 dispatch_async(dispatch_get_main_queue(), ^{
1916 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1917 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1919 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1920 [node forceRefresh];
1922 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1925 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1926 [self forceRefresh:self];
1927 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1928 [self refresh:self];
1930 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1931 [self requestFailed:objectRequest];
1936 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1937 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1938 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1939 NSLog(@"Copy object finished: %@", objectRequest.url);
1940 if (operation.isCancelled) {
1941 [self requestFailed:objectRequest];
1942 } else if (objectRequest.responseStatusCode == 201) {
1943 dispatch_async(dispatch_get_main_queue(), ^{
1944 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1945 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1947 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1948 [node forceRefresh];
1950 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1953 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1954 [self forceRefresh:self];
1955 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1956 [self refresh:self];
1958 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1959 [self requestFailed:objectRequest];
1964 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1965 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1966 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1967 NSLog(@"Delete object finished: %@", objectRequest.url);
1968 if (operation.isCancelled) {
1969 [self requestFailed:objectRequest];
1970 } else if (objectRequest.responseStatusCode == 204) {
1971 dispatch_async(dispatch_get_main_queue(), ^{
1972 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1973 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1975 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1976 [node forceRefresh];
1978 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1981 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1982 [self forceRefresh:self];
1983 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1984 [self refresh:self];
1986 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1987 [self requestFailed:objectRequest];
1993 #pragma mark NSSplitViewDelegate
1995 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1996 if (splitView == verticalSplitView)
1999 return ([horizontalSplitView bounds].size.height - 108);
2002 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
2003 if (splitView == verticalSplitView)
2006 return ([horizontalSplitView bounds].size.height - 108);
2009 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2010 if (splitView == verticalSplitView) {
2011 if (proposedPosition < 120)
2013 else if (proposedPosition > 220)
2016 return proposedPosition;
2018 return ([horizontalSplitView bounds].size.height - 108);
2023 #pragma mark NSOutlineViewDataSource
2025 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2028 if (item == containersNode)
2029 return containersNodeChildren.count;
2030 if (item == sharedNode)
2035 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2037 return (!index ? containersNode : sharedNode);
2038 if (item == sharedNode)
2039 return (!index ? mySharedNode : othersSharedNode);
2040 return [containersNodeChildren objectAtIndex:index];
2043 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2044 if ((item == containersNode) || (item == sharedNode))
2049 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2050 PithosNode *node = (PithosNode *)item;
2054 #pragma mark Drag and Drop destination
2056 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2057 validateDrop:(id<NSDraggingInfo>)info
2058 proposedItem:(id)item
2059 proposedChildIndex:(NSInteger)index {
2060 NSDragOperation result = NSDragOperationNone;
2061 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2063 PithosNode *dropNode = (PithosNode *)item;
2064 if ([dropNode class] != [PithosContainerNode class])
2066 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2067 result = NSDragOperationCopy;
2068 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2069 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
2070 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2071 if (![dropNode isEqualTo:draggedParentNode])
2072 result = NSDragOperationMove;
2073 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2074 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2075 result = NSDragOperationCopy;
2081 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2082 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2083 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2084 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2085 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2086 PithosNode *node = (PithosNode *)item;
2087 NSLog(@"drag in node: %@", node.url);
2088 return [self uploadFiles:filenames toNode:node];
2090 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2091 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2092 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2093 PithosNode *node = (PithosNode *)item;
2094 NSLog(@"drag local node: %@", node.url);
2095 if ([info draggingSourceOperationMask] & NSDragOperationMove)
2096 return [self moveNodes:draggedNodes toNode:node];
2097 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2098 return [self copyNodes:draggedNodes toNode:node];
2105 #pragma mark NSOutlineViewDelegate
2107 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2108 if ((item == containersNode) || (item == sharedNode))
2113 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2114 if ((item == containersNode) || (item == sharedNode))
2119 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2120 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2123 [browser loadColumnZero];
2129 #pragma mark NSMenuDelegate
2131 - (void)menuNeedsUpdate:(NSMenu *)menu {
2132 [menu removeAllItems];
2133 NSMenuItem *menuItem;
2134 NSString *menuItemTitle;
2135 BOOL nodeContextMenu = NO;
2136 PithosNode *menuNode = nil;
2137 NSMutableArray *menuNodes;
2138 if (menu == browserMenu) {
2139 NSInteger column = [browser clickedColumn];
2140 NSInteger row = [browser clickedRow];
2141 if ((column == -1) || (row == -1)) {
2142 // General context menu
2143 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2144 if ([menuNodesIndexPaths count] == 0) {
2145 menuNode = [browser parentForItemsInColumn:0];
2146 } else if (([menuNodesIndexPaths count] != 1) ||
2147 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2148 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2150 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2153 // Node context menu
2154 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2155 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2156 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2157 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2158 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2159 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2162 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2164 nodeContextMenu = YES;
2166 } else if (menu == outlineViewMenu) {
2167 NSInteger row = [outlineView clickedRow];
2169 row = [outlineView selectedRow];
2172 menuNode = [outlineView itemAtRow:row];
2175 if (!nodeContextMenu) {
2176 // General context menu
2177 if (([menuNode class] == [PithosAccountNode class]) ||
2178 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2179 ([menuNode class] == [PithosEmptyNode class]))
2181 BOOL shared = menuNode.shared;
2182 BOOL sharingAccount = (menuNode.sharingAccount != nil);
2184 if (!shared && !sharingAccount) {
2185 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2186 [menuItem setRepresentedObject:menuNode];
2187 [menu addItem:menuItem];
2188 [menu addItem:[NSMenuItem separatorItem]];
2191 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2192 [menu addItem:menuItem];
2193 [menu addItem:[NSMenuItem separatorItem]];
2195 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2196 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2197 [menu addItem:menuItem];
2199 if (!shared && !sharingAccount) {
2200 if (clipboardNodes) {
2201 NSUInteger clipboardNodesCount = [clipboardNodes count];
2202 if (clipboardNodesCount == 0) {
2203 self.clipboardNodes = nil;
2204 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2205 if (clipboardNodesCount == 1)
2206 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2208 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2209 [menu addItem:[NSMenuItem separatorItem]];
2210 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2211 [menuItem setRepresentedObject:menuNode];
2212 [menu addItem:menuItem];
2217 // Node context menu
2218 NSUInteger menuNodesCount = [menuNodes count];
2219 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
2220 BOOL shared = firstMenuNode.shared;
2221 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
2222 // Move to Trash (pithos container only)
2224 if (!shared && !sharingAccount) {
2225 if ([rootNode class] == [PithosContainerNode class]) {
2226 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2227 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
2228 [menuItem setRepresentedObject:menuNodes];
2229 [menu addItem:menuItem];
2231 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2232 [menuItem setRepresentedObject:menuNodes];
2233 [menu addItem:menuItem];
2234 [menu addItem:[NSMenuItem separatorItem]];
2238 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2239 [menu addItem:menuItem];
2241 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2242 [menu addItem:[NSMenuItem separatorItem]];
2243 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2244 [menuItem setRepresentedObject:menuNodes];
2245 [menu addItem:menuItem];
2247 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2248 [menu addItem:[NSMenuItem separatorItem]];
2251 if (!shared && !sharingAccount) {
2252 if (menuNodesCount == 1)
2253 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2255 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2256 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2257 [menuItem setRepresentedObject:menuNodes];
2258 [menu addItem:menuItem];
2261 if ((!shared && !sharingAccount) ||
2262 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2263 if (menuNodesCount == 1)
2264 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2266 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2267 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2268 [menuItem setRepresentedObject:menuNodes];
2269 [menu addItem:menuItem];
2272 if (!shared && !sharingAccount) {
2273 if (menuNodesCount == 1) {
2274 PithosNode *menuNode = [menuNodes objectAtIndex:0];
2275 if (([menuNode class] == [PithosSubdirNode class]) &&
2276 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
2277 if (clipboardNodes) {
2278 NSUInteger clipboardNodesCount = [clipboardNodes count];
2279 if (clipboardNodesCount == 0) {
2280 self.clipboardNodes = nil;
2281 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2282 if (clipboardNodesCount == 1)
2283 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2285 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2286 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2287 [menuItem setRepresentedObject:menuNode];
2288 [menu addItem:menuItem];
2298 #pragma mark Menu Actions
2300 - (void)menuNewFolder:(NSMenuItem *)sender {
2301 PithosNode *node = (PithosNode *)[sender representedObject];
2302 if ([node class] == [PithosContainerNode class]) {
2303 // Operation: Create (upload) a new root application/directory object
2304 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2305 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2306 if (operation.isCancelled) {
2310 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2311 containerName:node.pithosContainer.name
2312 subdirName:@"untitled folder"];
2313 NSString *fileName = [safeObjectName lastPathComponent];
2314 if (operation.isCancelled) {
2318 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2319 containerName:node.pithosContainer.name
2320 objectName:safeObjectName
2322 contentType:@"application/directory"
2324 contentDisposition:nil
2327 isPublic:ASIPithosObjectRequestPublicIgnore
2329 data:[NSData data]];
2330 objectRequest.delegate = self;
2331 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2332 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2333 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2334 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2335 message:messagePrefix];
2336 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2337 fileName, @"fileName",
2338 [NSArray arrayWithObject:node], @"refreshNodes",
2339 [NSNumber numberWithBool:YES], @"refresh",
2340 activity, @"activity",
2341 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2342 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2343 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2344 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2345 [NSNumber numberWithUnsignedInteger:10], @"retries",
2346 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2347 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2348 uploadNetworkQueue, @"networkQueue",
2349 @"upload", @"operationType",
2351 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2354 [uploadQueue addOperation:operation];
2355 } else if (([node class] == [PithosSubdirNode class]) &&
2356 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2357 // Operation: Create (upload) a new aplication/directory object
2358 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2359 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2360 if (operation.isCancelled) {
2364 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2365 containerName:node.pithosContainer.name
2366 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2367 NSString *fileName = [safeObjectName lastPathComponent];
2368 if (operation.isCancelled) {
2372 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2373 containerName:node.pithosContainer.name
2374 objectName:safeObjectName
2376 contentType:@"application/directory"
2378 contentDisposition:nil
2381 isPublic:ASIPithosObjectRequestPublicIgnore
2383 data:[NSData data]];
2384 objectRequest.delegate = self;
2385 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2386 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2387 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2388 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2389 message:messagePrefix];
2390 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2391 fileName, @"fileName",
2392 [NSArray arrayWithObject:node], @"refreshNodes",
2393 [NSNumber numberWithBool:YES], @"refresh",
2394 activity, @"activity",
2395 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2396 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2397 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2398 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2399 [NSNumber numberWithUnsignedInteger:10], @"retries",
2400 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2401 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2402 uploadNetworkQueue, @"networkQueue",
2403 @"upload", @"operationType",
2405 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2408 [uploadQueue addOperation:operation];
2412 - (void)menuGetInfo:(NSMenuItem *)sender {
2413 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2414 [node showPithosNodeInfo:sender];
2418 - (void)menuDelete:(NSMenuItem *)sender {
2419 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2420 if (([node class] == [PithosObjectNode class]) ||
2421 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2422 // Operation: Delete an object or subdir/ node
2423 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2424 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2425 if (operation.isCancelled) {
2429 NSString *fileName = [node.pithosObject.name lastPathComponent];
2430 if ([node.pithosObject.name hasSuffix:@"/"])
2431 fileName = [fileName stringByAppendingString:@"/"];
2432 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2433 containerName:node.pithosContainer.name
2434 objectName:node.pithosObject.name];
2435 if (operation.isCancelled) {
2439 objectRequest.delegate = self;
2440 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2441 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2442 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2443 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2444 message:messagePrefix];
2445 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2446 fileName, @"fileName",
2447 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2448 activity, @"activity",
2449 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2450 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2451 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2452 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2453 [NSNumber numberWithUnsignedInteger:10], @"retries",
2454 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2455 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2456 deleteNetworkQueue, @"networkQueue",
2457 @"delete", @"operationType",
2459 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2462 [deleteQueue addOperation:operation];
2463 } else if ([node class] == [PithosSubdirNode class]) {
2464 // Operation: Delete a subdir node and its descendants
2465 // The resulting ASIPithosObjectRequests are chained through dependencies
2466 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2467 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2468 if (operation.isCancelled) {
2472 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2473 containerName:node.pithosContainer.name
2474 objectName:node.pithosObject.name];
2475 if (!operation.isCancelled && objectRequests) {
2476 ASIPithosObjectRequest *previousObjectRequest = nil;
2477 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2478 if (operation.isCancelled) {
2482 objectRequest.delegate = self;
2483 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2484 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2485 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2486 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2487 message:messagePrefix];
2488 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2489 [NSDictionary dictionaryWithObjectsAndKeys:
2490 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2491 activity, @"activity",
2492 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2493 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2494 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2495 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2496 [NSNumber numberWithUnsignedInteger:10], @"retries",
2497 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2498 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2499 deleteNetworkQueue, @"networkQueue",
2500 @"delete", @"operationType",
2502 if (previousObjectRequest)
2503 [objectRequest addDependency:previousObjectRequest];
2504 previousObjectRequest = objectRequest;
2505 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2510 [deleteQueue addOperation:operation];
2515 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2516 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2517 if (([node class] == [PithosObjectNode class]) ||
2518 (([node class] == [PithosSubdirNode class]) &&
2519 !node.pithosObject.subdir &&
2520 [node.pithosObject.name hasSuffix:@"/"])) {
2521 // Operation: Move to trash an object or subdir/ node
2522 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2523 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2524 if (operation.isCancelled) {
2528 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2529 containerName:@"trash"
2530 objectName:node.pithosObject.name];
2531 if (!operation.isCancelled && safeObjectName) {
2532 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2533 containerName:node.pithosContainer.name
2534 objectName:node.pithosObject.name
2535 destinationContainerName:@"trash"
2536 destinationObjectName:safeObjectName
2538 if (!operation.isCancelled && objectRequest) {
2539 objectRequest.delegate = self;
2540 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2541 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2542 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2543 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2544 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2545 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2546 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2547 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2548 message:messagePrefix];
2549 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2550 [NSDictionary dictionaryWithObjectsAndKeys:
2551 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2552 activity, @"activity",
2553 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2554 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2555 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2556 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2557 [NSNumber numberWithUnsignedInteger:10], @"retries",
2558 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2559 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2560 moveNetworkQueue, @"networkQueue",
2561 @"move", @"operationType",
2563 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2568 [moveQueue addOperation:operation];
2569 } else if ([node class] == [PithosSubdirNode class]) {
2570 // Operation: Move to trash a subdir node and its descendants
2571 // The resulting ASIPithosObjectRequests are chained through dependencies
2572 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2573 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2574 if (operation.isCancelled) {
2578 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2579 containerName:@"trash"
2580 subdirName:node.pithosObject.name];
2581 if (!operation.isCancelled && safeObjectName) {
2582 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2583 containerName:node.pithosContainer.name
2584 objectName:node.pithosObject.name
2585 destinationContainerName:@"trash"
2586 destinationObjectName:safeObjectName
2588 if (!operation.isCancelled && objectRequests) {
2589 ASIPithosObjectRequest *previousObjectRequest = nil;
2590 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2591 if (operation.isCancelled) {
2595 objectRequest.delegate = self;
2596 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2597 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2598 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2599 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2600 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2601 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2602 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2603 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2604 message:messagePrefix];
2605 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2606 [NSDictionary dictionaryWithObjectsAndKeys:
2607 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2608 activity, @"activity",
2609 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2610 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2611 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2612 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2613 [NSNumber numberWithUnsignedInteger:10], @"retries",
2614 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2615 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2616 moveNetworkQueue, @"networkQueue",
2617 @"move", @"operationType",
2619 if (previousObjectRequest)
2620 [objectRequest addDependency:previousObjectRequest];
2621 previousObjectRequest = objectRequest;
2622 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2628 [moveQueue addOperation:operation];
2633 - (void)menuCut:(NSMenuItem *)sender {
2634 self.clipboardNodes = (NSArray *)[sender representedObject];
2635 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2636 self.clipboardCopy = NO;
2639 - (void)menuCopy:(NSMenuItem *)sender {
2640 self.clipboardNodes = (NSArray *)[sender representedObject];
2641 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2642 self.clipboardCopy = YES;
2645 - (void)menuPaste:(NSMenuItem *)sender {
2646 if (!clipboardNodes || ![clipboardNodes count])
2648 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2649 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2650 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2651 self.clipboardNodes = nil;
2652 self.clipboardParentNode = nil;
2653 [self moveNodes:localClipboardNodes toNode:dropNode];
2655 [self copyNodes:localClipboardNodes toNode:dropNode];
2660 #pragma mark PithosActivityFacilityDelegate
2662 - (void)activityUpdate:(NSDictionary *)info {
2663 NSString *message = [info objectForKey:@"message"];
2664 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2665 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2666 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2667 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2668 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2669 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2670 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2671 if (runningActivitiesCount && totalBytes) {
2672 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2673 [activityProgressIndicator startAnimation:self];
2675 [activityProgressIndicator setDoubleValue:1.0];
2676 [activityProgressIndicator stopAnimation:self];
2680 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2681 [activityTextField setStringValue:message];