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 // If the node is a subdir ask if the whole tree should be downloaded
811 if ([node class] == [PithosSubdirNode class]) {
812 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
813 [alert setMessageText:@"Download directory"];
814 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
815 [alert addButtonWithTitle:@"OK"];
816 [alert addButtonWithTitle:@"Cancel"];
817 NSInteger choice = [alert runModal];
818 if (choice == NSAlertFirstButtonReturn) {
819 // Operation: Download a subdir node and its descendants
820 // The resulting ASIPithosObjectRequests are chained through dependencies
821 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
822 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
823 if (operation.isCancelled) {
827 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
828 containerName:node.pithosContainer.name
829 objectName:node.pithosObject.name
830 toDirectory:[dropDestination path]
832 sharingAccount:node.sharingAccount];
833 if (!operation.isCancelled && objectRequests) {
834 ASIPithosObjectRequest *previousObjectRequest = nil;
835 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
836 if (operation.isCancelled) {
840 [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]];
841 objectRequest.delegate = self;
842 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
843 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
844 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
845 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
846 message:[messagePrefix stringByAppendingString:@" (0%)"]
847 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
849 dispatch_async(dispatch_get_main_queue(), ^{
850 [activityFacility updateActivity:activity withMessage:activity.message];
852 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
853 [NSDictionary dictionaryWithObjectsAndKeys:
854 activity, @"activity",
855 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
856 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
857 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
858 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
859 [NSNumber numberWithUnsignedInteger:10], @"retries",
860 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
861 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
862 downloadNetworkQueue, @"networkQueue",
863 @"download", @"operationType",
865 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
866 [activityFacility updateActivity:activity
867 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
868 totalBytes:activity.totalBytes
869 currentBytes:(activity.currentBytes + size)];
871 if (previousObjectRequest)
872 [objectRequest addDependency:previousObjectRequest];
873 previousObjectRequest = objectRequest;
874 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
879 [downloadQueue addOperation:operation];
882 // Operation: Download an object node
883 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
884 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
885 if (operation.isCancelled) {
889 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
890 containerName:node.pithosContainer.name
891 objectName:node.pithosObject.name
892 toDirectory:[dropDestination path]
894 sharingAccount:node.sharingAccount];
895 if (!operation.isCancelled && objectRequest) {
896 [names addObject:[objectRequest.userInfo objectForKey:@"fileName"]];
897 objectRequest.delegate = self;
898 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
899 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
900 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
901 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
902 message:[messagePrefix stringByAppendingString:@" (0%)"]
903 totalBytes:node.pithosObject.bytes
905 dispatch_async(dispatch_get_main_queue(), ^{
906 [activityFacility updateActivity:activity withMessage:activity.message];
908 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
909 [NSDictionary dictionaryWithObjectsAndKeys:
910 activity, @"activity",
911 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
912 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
913 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
914 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
915 [NSNumber numberWithUnsignedInteger:10], @"retries",
916 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
917 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
918 downloadNetworkQueue, @"networkQueue",
919 @"download", @"operationType",
921 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
922 [activityFacility updateActivity:activity
923 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
924 totalBytes:activity.totalBytes
925 currentBytes:(activity.currentBytes + size)];
927 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
931 [downloadQueue addOperation:operation];
937 #pragma mark Drag and Drop destination
939 - (NSDragOperation)browser:aBrowser
940 validateDrop:(id<NSDraggingInfo>)info
941 proposedRow:(NSInteger *)row
942 column:(NSInteger *)column
943 dropOperation:(NSBrowserDropOperation *)dropOperation {
944 NSDragOperation result = NSDragOperationNone;
945 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
946 // For a drop above, the drop is redirected to the parent item
947 if (*dropOperation == NSBrowserDropAbove)
949 // Only allow dropping in folders
951 PithosNode *dropNode;
953 // Check if the node is not a folder and if so redirect to the parent item
954 dropNode = [browser itemAtRow:*row inColumn:*column];
955 if ([dropNode class] == [PithosObjectNode class])
959 dropNode = [browser parentForItemsInColumn:*column];
961 if (!dropNode.shared &&
962 (!dropNode.sharingAccount ||
963 ([dropNode class] == [PithosSubdirNode class]) ||
964 ([dropNode class] == [PithosContainerNode class]))) {
965 *dropOperation = NSBrowserDropOn;
966 result = NSDragOperationCopy;
969 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
970 // For a drop above, the drop is redirected to the parent item
971 if (*dropOperation == NSBrowserDropAbove)
973 // Only allow dropping in folders
975 PithosNode *dropNode;
977 // Check if the node is not a folder and if so redirect to the parent item
978 dropNode = [browser itemAtRow:*row inColumn:*column];
979 if ([dropNode class] == [PithosObjectNode class])
983 dropNode = [browser parentForItemsInColumn:*column];
985 if (!dropNode.shared && !dropNode.sharingAccount) {
986 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
987 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
988 if ((([dropNode class] == [PithosContainerNode class]) ||
989 dropNode.pithosObject.subdir ||
990 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
991 ![dropNode isEqualTo:draggedParentNode]) {
992 // ![dropNode isEqualTo:draggedParentNode] &&
993 // ![draggedNodes containsObject:dropNode]) {
994 result = NSDragOperationMove;
996 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
997 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
998 if (([dropNode class] == [PithosContainerNode class]) ||
999 dropNode.pithosObject.subdir ||
1000 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
1001 result = NSDragOperationCopy;
1010 - (BOOL)browser:(NSBrowser *)aBrowser
1011 acceptDrop:(id<NSDraggingInfo>)info
1012 atRow:(NSInteger)row
1013 column:(NSInteger)column
1014 dropOperation:(NSBrowserDropOperation)dropOperation {
1015 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1016 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1017 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1018 if ((column != -1) && (filenames != nil)) {
1021 node = [browser itemAtRow:row inColumn:column];
1023 node = [browser parentForItemsInColumn:column];
1024 NSLog(@"drag in node: %@", node.url);
1025 return [self uploadFiles:filenames toNode:node];
1027 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1028 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1029 if ((column != -1) && (draggedNodes != nil)) {
1032 node = [browser itemAtRow:row inColumn:column];
1034 node = [browser parentForItemsInColumn:column];
1035 NSLog(@"drag local node: %@", node.url);
1036 if ([info draggingSourceOperationMask] & NSDragOperationMove)
1037 return [self moveNodes:draggedNodes toNode:node];
1038 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1039 return [self copyNodes:draggedNodes toNode:node];
1046 #pragma mark Drag and Drop methods
1048 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1049 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1051 NSFileManager *fileManager = [NSFileManager defaultManager];
1052 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1053 NSString *objectNamePrefix;
1054 if ([destinationNode class] == [PithosSubdirNode class])
1055 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1057 objectNamePrefix = [NSString string];
1058 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1059 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1060 containerName:containerName];
1061 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1063 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1064 if ([containerRequest error]) {
1065 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1067 } else if (containerRequest.responseStatusCode != 204) {
1068 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1071 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1072 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1074 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1075 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1077 for (NSString *filePath in filenames) {
1079 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1082 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1083 // Operation: Upload a local file
1084 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1085 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1086 if (operation.isCancelled) {
1090 NSError *error = nil;
1091 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1092 if (contentType == nil)
1093 contentType = @"application/octet-stream";
1095 NSLog(@"contentType detection error: %@", error);
1096 NSArray *hashes = nil;
1097 if (operation.isCancelled) {
1101 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1102 containerName:containerName
1103 objectName:objectName
1104 contentType:contentType
1110 sharingAccount:destinationNode.sharingAccount];
1111 if (!operation.isCancelled && objectRequest) {
1112 objectRequest.delegate = self;
1113 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1114 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1115 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1116 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1117 message:[messagePrefix stringByAppendingString:@" (0%)"]
1118 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1120 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1121 [NSDictionary dictionaryWithObjectsAndKeys:
1122 containerName, @"containerName",
1123 objectName, @"objectName",
1124 contentType, @"contentType",
1125 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1126 blockHash, @"blockHash",
1127 filePath, @"filePath",
1129 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1130 [NSNumber numberWithBool:YES], @"refresh",
1131 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1132 activity, @"activity",
1133 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1134 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1135 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1136 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1137 [NSNumber numberWithUnsignedInteger:10], @"retries",
1138 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1139 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1140 uploadNetworkQueue, @"networkQueue",
1141 @"upload", @"operationType",
1143 if (destinationNode.sharingAccount)
1144 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1145 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1149 [uploadQueue addOperation:operation];
1151 // Upload directory, confirm first
1152 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1153 [alert setMessageText:@"Upload directory"];
1154 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1155 [alert addButtonWithTitle:@"OK"];
1156 [alert addButtonWithTitle:@"Cancel"];
1157 NSInteger choice = [alert runModal];
1158 if (choice == NSAlertFirstButtonReturn) {
1159 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
1160 // Operation: Upload a local directory and its descendants
1161 // The resulting ASIPithosObjectRequests are chained through dependencies
1162 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1163 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1164 if (operation.isCancelled) {
1168 NSMutableArray *objectNames = nil;
1169 NSMutableArray *contentTypes = nil;
1170 NSMutableArray *filePaths = nil;
1171 NSMutableArray *hashesArrays = nil;
1172 NSMutableArray *directoryObjectRequests = nil;
1173 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1174 containerName:containerName
1175 objectName:objectName
1178 forDirectory:filePath
1180 objectNames:&objectNames
1181 contentTypes:&contentTypes
1182 filePaths:&filePaths
1183 hashesArrays:&hashesArrays
1184 directoryObjectRequests:&directoryObjectRequests
1185 sharingAccount:destinationNode.sharingAccount];
1186 if (operation.isCancelled) {
1190 ASIPithosObjectRequest *previousObjectRequest = nil;
1191 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1192 if (operation.isCancelled) {
1196 objectRequest.delegate = self;
1197 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1198 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1199 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1200 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1201 message:messagePrefix];
1202 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1203 [NSDictionary dictionaryWithObjectsAndKeys:
1204 [NSNumber numberWithBool:YES], @"refresh",
1205 activity, @"activity",
1206 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1207 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1208 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1209 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1210 [NSNumber numberWithUnsignedInteger:10], @"retries",
1211 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1212 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1213 uploadNetworkQueue, @"networkQueue",
1214 @"upload", @"queue",
1216 if (previousObjectRequest)
1217 [objectRequest addDependency:previousObjectRequest];
1218 previousObjectRequest = objectRequest;
1219 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1221 if (!operation.isCancelled && objectRequests) {
1222 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1223 if (operation.isCancelled) {
1227 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1228 objectRequest.delegate = self;
1229 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1230 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1231 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1232 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1233 message:[messagePrefix stringByAppendingString:@" (0%)"]
1234 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1236 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1237 [NSDictionary dictionaryWithObjectsAndKeys:
1238 containerName, @"containerName",
1239 [objectNames objectAtIndex:i], @"objectName",
1240 [contentTypes objectAtIndex:i], @"contentType",
1241 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1242 blockHash, @"blockHash",
1243 [filePaths objectAtIndex:i], @"filePath",
1244 [hashesArrays objectAtIndex:i], @"hashes",
1245 [NSNumber numberWithBool:YES], @"refresh",
1246 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1247 activity, @"activity",
1248 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1249 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1250 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1251 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1252 [NSNumber numberWithUnsignedInteger:10], @"retries",
1253 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1254 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1255 uploadNetworkQueue, @"networkQueue",
1256 @"upload", @"queue",
1258 if (destinationNode.sharingAccount)
1259 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1260 if (previousObjectRequest)
1261 [objectRequest addDependency:previousObjectRequest];
1262 previousObjectRequest = objectRequest;
1263 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1268 [uploadQueue addOperation:operation];
1276 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1277 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1278 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1280 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1281 NSString *objectNamePrefix;
1282 if ([destinationNode class] == [PithosSubdirNode class])
1283 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1285 objectNamePrefix = [NSString string];
1287 for (PithosNode *node in nodes) {
1288 if (([node class] == [PithosObjectNode class]) ||
1289 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1290 // Operation: Move an object or subdir/ node
1291 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1292 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1293 if (operation.isCancelled) {
1297 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1298 if ([node.pithosObject.name hasSuffix:@"/"])
1299 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1300 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1301 containerName:node.pithosContainer.name
1302 objectName:node.pithosObject.name
1303 destinationContainerName:containerName
1304 destinationObjectName:destinationObjectName
1306 if (!operation.isCancelled && objectRequest) {
1307 objectRequest.delegate = self;
1308 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1309 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1310 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1311 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1312 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1313 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1314 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1315 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1316 message:messagePrefix];
1317 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1318 [NSDictionary dictionaryWithObjectsAndKeys:
1319 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1320 activity, @"activity",
1321 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1322 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1323 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1324 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1325 [NSNumber numberWithUnsignedInteger:10], @"retries",
1326 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1327 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1328 moveNetworkQueue, @"networkQueue",
1329 @"move", @"operationType",
1331 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1335 [moveQueue addOperation:operation];
1336 } else if ([node class] == [PithosSubdirNode class]) {
1337 // Operation: Move a subdir node and its descendants
1338 // The resulting ASIPithosObjectRequests are chained through dependencies
1339 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1340 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1341 if (operation.isCancelled) {
1345 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1346 if (node.pithosObject.subdir)
1347 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1348 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1349 containerName:node.pithosContainer.name
1350 objectName:node.pithosObject.name
1351 destinationContainerName:containerName
1352 destinationObjectName:destinationObjectName
1354 if (!operation.isCancelled && objectRequests) {
1355 ASIPithosObjectRequest *previousObjectRequest = nil;
1356 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1357 if (operation.isCancelled) {
1361 objectRequest.delegate = self;
1362 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1363 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1364 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1365 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1366 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1367 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1368 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1369 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1370 message:messagePrefix];
1371 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1372 [NSDictionary dictionaryWithObjectsAndKeys:
1373 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1374 [NSNumber numberWithBool:YES], @"refresh",
1375 activity, @"activity",
1376 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1377 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1378 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1379 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1380 [NSNumber numberWithUnsignedInteger:10], @"retries",
1381 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1382 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1383 moveNetworkQueue, @"networkQueue",
1384 @"move", @"operationType",
1386 if (previousObjectRequest)
1387 [objectRequest addDependency:previousObjectRequest];
1388 previousObjectRequest = objectRequest;
1389 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1394 [moveQueue addOperation:operation];
1400 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1401 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1402 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1404 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1405 NSString *objectNamePrefix;
1406 if ([destinationNode class] == [PithosSubdirNode class])
1407 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1409 objectNamePrefix = [NSString string];
1411 for (PithosNode *node in nodes) {
1412 if (([node class] == [PithosObjectNode class]) ||
1413 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1414 // Operation: Copy an object or subdir/ node
1415 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1416 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1417 if (operation.isCancelled) {
1421 NSString *destinationObjectName;
1422 if (![destinationNode isEqualTo:node.parent]) {
1423 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1424 if ([node.pithosObject.name hasSuffix:@"/"])
1425 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1427 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1428 containerName:containerName
1429 objectName:node.pithosObject.name];
1431 if (operation.isCancelled) {
1435 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos
1436 containerName:node.pithosContainer.name
1437 objectName:node.pithosObject.name
1438 destinationContainerName:containerName
1439 destinationObjectName:destinationObjectName
1441 sharingAccount:node.sharingAccount];
1442 if (!operation.isCancelled && objectRequest) {
1443 objectRequest.delegate = self;
1444 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1445 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1446 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1447 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1448 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1449 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1450 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1451 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1452 message:messagePrefix];
1453 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1454 [NSDictionary dictionaryWithObjectsAndKeys:
1455 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1456 activity, @"activity",
1457 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1458 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1459 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1460 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1461 [NSNumber numberWithUnsignedInteger:10], @"retries",
1462 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1463 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1464 copyNetworkQueue, @"networkQueue",
1465 @"copy", @"operationType",
1467 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1471 [copyQueue addOperation:operation];
1472 } else if ([node class] == [PithosSubdirNode class]) {
1473 // Operation: Copy a subdir node and its descendants
1474 // The resulting ASIPithosObjectRequests are chained through dependencies
1475 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1476 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1477 if (operation.isCancelled) {
1481 NSString *destinationObjectName;
1482 if (![destinationNode isEqualTo:node.parent]) {
1483 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1484 if (node.pithosObject.subdir)
1485 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1487 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1488 containerName:containerName
1489 subdirName:node.pithosObject.name];
1491 if (operation.isCancelled) {
1495 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos
1496 containerName:node.pithosContainer.name
1497 objectName:node.pithosObject.name
1498 destinationContainerName:containerName
1499 destinationObjectName:destinationObjectName
1501 sharingAccount:node.sharingAccount];
1502 if (!operation.isCancelled && objectRequests) {
1503 ASIPithosObjectRequest *previousObjectRequest = nil;
1504 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1505 if (operation.isCancelled) {
1509 objectRequest.delegate = self;
1510 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1511 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1512 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1513 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1514 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1515 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1516 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1517 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1518 message:messagePrefix];
1519 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1520 [NSDictionary dictionaryWithObjectsAndKeys:
1521 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1522 activity, @"activity",
1523 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1524 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1525 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1526 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1527 [NSNumber numberWithUnsignedInteger:10], @"retries",
1528 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1529 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1530 copyNetworkQueue, @"networkQueue",
1531 @"copy", @"operationType",
1533 if (previousObjectRequest)
1534 [objectRequest addDependency:previousObjectRequest];
1535 previousObjectRequest = objectRequest;
1536 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1541 [copyQueue addOperation:operation];
1548 #pragma mark ASIHTTPRequestDelegate
1550 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1551 NSOperationQueue *callbackQueue;
1552 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1553 if ([operationType isEqualToString:@"move"])
1554 callbackQueue = moveCallbackQueue;
1555 else if ([operationType isEqualToString:@"copy"])
1556 callbackQueue = copyCallbackQueue;
1557 else if ([operationType isEqualToString:@"delete"])
1558 callbackQueue = deleteCallbackQueue;
1559 else if ([operationType isEqualToString:@"upload"])
1560 callbackQueue = uploadCallbackQueue;
1561 else if ([operationType isEqualToString:@"download"])
1562 callbackQueue = downloadCallbackQueue;
1564 dispatch_async(dispatch_get_main_queue(), ^{
1565 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1566 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1570 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1571 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1572 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1573 object:request] autorelease];
1574 operation.completionBlock = ^{
1575 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1576 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1577 dispatch_async(dispatch_get_main_queue(), ^{
1578 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1579 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1584 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1585 [callbackQueue addOperation:operation];
1588 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1589 if (request.isCancelled) {
1590 // Request has been cancelled
1591 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1592 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1593 withObject:request];
1595 NSOperationQueue *callbackQueue;
1596 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1597 if ([operationType isEqualToString:@"move"])
1598 callbackQueue = moveCallbackQueue;
1599 else if ([operationType isEqualToString:@"copy"])
1600 callbackQueue = copyCallbackQueue;
1601 else if ([operationType isEqualToString:@"delete"])
1602 callbackQueue = deleteCallbackQueue;
1603 else if ([operationType isEqualToString:@"upload"])
1604 callbackQueue = uploadCallbackQueue;
1605 else if ([operationType isEqualToString:@"download"])
1606 callbackQueue = downloadCallbackQueue;
1608 dispatch_async(dispatch_get_main_queue(), ^{
1609 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1610 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1614 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1615 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1616 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1617 object:request] autorelease];
1618 operation.completionBlock = ^{
1619 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1620 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1621 dispatch_async(dispatch_get_main_queue(), ^{
1622 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1623 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1628 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1629 [callbackQueue addOperation:operation];
1633 - (void)requestFailed:(ASIPithosRequest *)request {
1634 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1635 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1636 NSLog(@"Request failed: %@", request.url);
1637 if (operation.isCancelled) {
1641 if (request.isCancelled) {
1642 dispatch_async(dispatch_get_main_queue(), ^{
1643 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1644 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1649 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1651 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1652 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1653 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1654 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1656 dispatch_async(dispatch_get_main_queue(), ^{
1657 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1658 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1659 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1660 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1662 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1668 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1669 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1670 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1671 NSLog(@"Download finished: %@", objectRequest.url);
1672 if (operation.isCancelled) {
1673 [self requestFailed:objectRequest];
1674 } else if (objectRequest.responseStatusCode == 200) {
1675 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1676 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1677 NSUInteger totalBytes = activity.totalBytes;
1679 // XXX change contentLength to objectContentLength if it is fixed in the server
1680 if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1681 NSLog(@"Downloaded 0 bytes");
1682 NSFileManager *fileManager = [NSFileManager defaultManager];
1683 if (![fileManager fileExistsAtPath:filePath]) {
1684 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1685 dispatch_async(dispatch_get_main_queue(), ^{
1686 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1687 [alert setMessageText:@"Create File Error"];
1688 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1689 [alert addButtonWithTitle:@"OK"];
1696 NSUInteger currentBytes = [objectRequest objectContentLength];
1697 if (currentBytes == 0)
1698 currentBytes = totalBytes;
1699 dispatch_async(dispatch_get_main_queue(), ^{
1700 [activityFacility endActivity:activity
1701 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1702 totalBytes:totalBytes
1703 currentBytes:currentBytes];
1706 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1707 [self requestFailed:objectRequest];
1712 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1713 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1714 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1715 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1716 if (operation.isCancelled) {
1717 [self requestFailed:objectRequest];
1718 } else if (objectRequest.responseStatusCode == 201) {
1719 NSLog(@"Directory object created: %@", objectRequest.url);
1720 dispatch_async(dispatch_get_main_queue(), ^{
1721 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1722 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1724 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1725 [node forceRefresh];
1727 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1730 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1731 [self forceRefresh:self];
1732 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1733 [self refresh:self];
1735 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1736 [self requestFailed:objectRequest];
1741 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1742 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1743 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1744 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1745 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1746 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1747 NSUInteger totalBytes = activity.totalBytes;
1748 NSUInteger currentBytes = activity.currentBytes;
1749 if (operation.isCancelled) {
1750 [self requestFailed:objectRequest];
1751 } else if (objectRequest.responseStatusCode == 201) {
1752 NSLog(@"Object created: %@", objectRequest.url);
1753 dispatch_async(dispatch_get_main_queue(), ^{
1754 [activityFacility endActivity:activity
1755 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1756 totalBytes:totalBytes
1757 currentBytes:totalBytes];
1759 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1760 [node forceRefresh];
1762 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1765 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1766 [self forceRefresh:self];
1767 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1768 [self refresh:self];
1769 } else if (objectRequest.responseStatusCode == 409) {
1770 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1771 if (iteration == 0) {
1772 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1773 dispatch_async(dispatch_get_main_queue(), ^{
1774 [activityFacility endActivity:activity
1775 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1776 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1777 [alert setMessageText:@"Upload Timeout"];
1778 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1779 [objectRequest.userInfo objectForKey:@"objectName"]]];
1780 [alert addButtonWithTitle:@"OK"];
1786 NSLog(@"object is missing hashes: %@", objectRequest.url);
1787 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1788 withMissingHashes:[objectRequest hashes]];
1789 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1790 if (totalBytes >= [missingBlocks count]*blockSize)
1791 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1792 dispatch_async(dispatch_get_main_queue(), ^{
1793 [activityFacility updateActivity:activity
1794 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1795 totalBytes:totalBytes
1796 currentBytes:currentBytes];
1798 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1799 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1800 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1802 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1803 missingBlockIndex:missingBlockIndex
1804 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1805 newContainerRequest.delegate = self;
1806 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1807 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1808 newContainerRequest.userInfo = objectRequest.userInfo;
1809 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1810 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1811 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1812 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1813 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1814 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1815 [activityFacility updateActivity:activity
1816 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1817 totalBytes:activity.totalBytes
1818 currentBytes:(activity.currentBytes + size)];
1820 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1822 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1823 [self requestFailed:objectRequest];
1828 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1829 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1830 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1831 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1832 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1833 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1834 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1835 if (operation.isCancelled) {
1836 [self requestFailed:containerRequest];
1837 } else if (containerRequest.responseStatusCode == 202) {
1838 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1839 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1840 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1841 if (missingBlockIndex == NSNotFound) {
1842 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1843 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1844 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1845 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1846 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1848 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1849 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1852 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1853 newObjectRequest.delegate = self;
1854 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1855 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1856 newObjectRequest.userInfo = containerRequest.userInfo;
1857 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1858 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1859 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1860 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1861 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1863 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1864 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1865 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1866 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1867 missingBlockIndex:missingBlockIndex
1868 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1869 newContainerRequest.delegate = self;
1870 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1871 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1872 newContainerRequest.userInfo = containerRequest.userInfo;
1873 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1874 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1875 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1876 [activityFacility updateActivity:activity
1877 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1878 totalBytes:activity.totalBytes
1879 currentBytes:(activity.currentBytes + size)];
1881 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1884 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1885 [self requestFailed:containerRequest];
1890 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1891 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1892 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1893 NSLog(@"Move object finished: %@", objectRequest.url);
1894 if (operation.isCancelled) {
1895 [self requestFailed:objectRequest];
1896 } else if (objectRequest.responseStatusCode == 201) {
1897 dispatch_async(dispatch_get_main_queue(), ^{
1898 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1899 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1901 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1902 [node forceRefresh];
1904 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1907 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1908 [self forceRefresh:self];
1909 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1910 [self refresh:self];
1912 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1913 [self requestFailed:objectRequest];
1918 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1919 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1920 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1921 NSLog(@"Copy object finished: %@", objectRequest.url);
1922 if (operation.isCancelled) {
1923 [self requestFailed:objectRequest];
1924 } else if (objectRequest.responseStatusCode == 201) {
1925 dispatch_async(dispatch_get_main_queue(), ^{
1926 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1927 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1929 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1930 [node forceRefresh];
1932 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1935 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1936 [self forceRefresh:self];
1937 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1938 [self refresh:self];
1940 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1941 [self requestFailed:objectRequest];
1946 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1947 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1948 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1949 NSLog(@"Delete object finished: %@", objectRequest.url);
1950 if (operation.isCancelled) {
1951 [self requestFailed:objectRequest];
1952 } else if (objectRequest.responseStatusCode == 204) {
1953 dispatch_async(dispatch_get_main_queue(), ^{
1954 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1955 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1957 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1958 [node forceRefresh];
1960 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1963 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1964 [self forceRefresh:self];
1965 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1966 [self refresh:self];
1968 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1969 [self requestFailed:objectRequest];
1975 #pragma mark NSSplitViewDelegate
1977 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1978 if (splitView == verticalSplitView)
1981 return ([horizontalSplitView bounds].size.height - 108);
1984 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1985 if (splitView == verticalSplitView)
1988 return ([horizontalSplitView bounds].size.height - 108);
1991 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1992 if (splitView == verticalSplitView) {
1993 if (proposedPosition < 120)
1995 else if (proposedPosition > 220)
1998 return proposedPosition;
2000 return ([horizontalSplitView bounds].size.height - 108);
2005 #pragma mark NSOutlineViewDataSource
2007 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2010 if (item == containersNode)
2011 return containersNodeChildren.count;
2012 if (item == sharedNode)
2017 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2019 return (!index ? containersNode : sharedNode);
2020 if (item == sharedNode)
2021 return (!index ? mySharedNode : othersSharedNode);
2022 return [containersNodeChildren objectAtIndex:index];
2025 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2026 if ((item == containersNode) || (item == sharedNode))
2031 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2032 PithosNode *node = (PithosNode *)item;
2036 #pragma mark Drag and Drop destination
2038 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2039 validateDrop:(id<NSDraggingInfo>)info
2040 proposedItem:(id)item
2041 proposedChildIndex:(NSInteger)index {
2042 NSDragOperation result = NSDragOperationNone;
2043 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2045 PithosNode *dropNode = (PithosNode *)item;
2046 if ([dropNode class] != [PithosContainerNode class])
2048 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2049 result = NSDragOperationCopy;
2050 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2051 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
2052 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2053 if (![dropNode isEqualTo:draggedParentNode])
2054 result = NSDragOperationMove;
2055 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2056 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2057 result = NSDragOperationCopy;
2063 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2064 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2065 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2066 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2067 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2068 PithosNode *node = (PithosNode *)item;
2069 NSLog(@"drag in node: %@", node.url);
2070 return [self uploadFiles:filenames toNode:node];
2072 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2073 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2074 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2075 PithosNode *node = (PithosNode *)item;
2076 NSLog(@"drag local node: %@", node.url);
2077 if ([info draggingSourceOperationMask] & NSDragOperationMove)
2078 return [self moveNodes:draggedNodes toNode:node];
2079 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2080 return [self copyNodes:draggedNodes toNode:node];
2087 #pragma mark NSOutlineViewDelegate
2089 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2090 if ((item == containersNode) || (item == sharedNode))
2095 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2096 if ((item == containersNode) || (item == sharedNode))
2101 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2102 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2105 [browser loadColumnZero];
2111 #pragma mark NSMenuDelegate
2113 - (void)menuNeedsUpdate:(NSMenu *)menu {
2114 [menu removeAllItems];
2115 NSMenuItem *menuItem;
2116 NSString *menuItemTitle;
2117 BOOL nodeContextMenu = NO;
2118 PithosNode *menuNode = nil;
2119 NSMutableArray *menuNodes;
2120 if (menu == browserMenu) {
2121 NSInteger column = [browser clickedColumn];
2122 NSInteger row = [browser clickedRow];
2123 if ((column == -1) || (row == -1)) {
2124 // General context menu
2125 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2126 if ([menuNodesIndexPaths count] == 0) {
2127 menuNode = [browser parentForItemsInColumn:0];
2128 } else if (([menuNodesIndexPaths count] != 1) ||
2129 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2130 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2132 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2135 // Node context menu
2136 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2137 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2138 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2139 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2140 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2141 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2144 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2146 nodeContextMenu = YES;
2148 } else if (menu == outlineViewMenu) {
2149 NSInteger row = [outlineView clickedRow];
2151 row = [outlineView selectedRow];
2154 menuNode = [outlineView itemAtRow:row];
2157 if (!nodeContextMenu) {
2158 // General context menu
2159 if (([menuNode class] == [PithosAccountNode class]) ||
2160 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2161 ([menuNode class] == [PithosEmptyNode class]))
2163 BOOL shared = menuNode.shared;
2164 BOOL sharingAccount = (menuNode.sharingAccount != nil);
2166 if (!shared && !sharingAccount) {
2167 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2168 [menuItem setRepresentedObject:menuNode];
2169 [menu addItem:menuItem];
2170 [menu addItem:[NSMenuItem separatorItem]];
2173 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2174 [menu addItem:menuItem];
2175 [menu addItem:[NSMenuItem separatorItem]];
2177 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2178 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2179 [menu addItem:menuItem];
2181 if (!shared && !sharingAccount) {
2182 if (clipboardNodes) {
2183 NSUInteger clipboardNodesCount = [clipboardNodes count];
2184 if (clipboardNodesCount == 0) {
2185 self.clipboardNodes = nil;
2186 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2187 if (clipboardNodesCount == 1)
2188 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2190 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2191 [menu addItem:[NSMenuItem separatorItem]];
2192 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2193 [menuItem setRepresentedObject:menuNode];
2194 [menu addItem:menuItem];
2199 // Node context menu
2200 NSUInteger menuNodesCount = [menuNodes count];
2201 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
2202 BOOL shared = firstMenuNode.shared;
2203 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
2204 // Move to Trash (pithos container only)
2206 if (!shared && !sharingAccount) {
2207 if ([rootNode class] == [PithosContainerNode class]) {
2208 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2209 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
2210 [menuItem setRepresentedObject:menuNodes];
2211 [menu addItem:menuItem];
2213 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2214 [menuItem setRepresentedObject:menuNodes];
2215 [menu addItem:menuItem];
2216 [menu addItem:[NSMenuItem separatorItem]];
2220 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2221 [menu addItem:menuItem];
2222 [menu addItem:[NSMenuItem separatorItem]];
2224 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2225 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
2226 [menuItem setRepresentedObject:menuNodes];
2227 [menu addItem:menuItem];
2229 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2230 [menu addItem:[NSMenuItem separatorItem]];
2233 if (!shared && !sharingAccount) {
2234 if (menuNodesCount == 1)
2235 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2237 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2238 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2239 [menuItem setRepresentedObject:menuNodes];
2240 [menu addItem:menuItem];
2243 if ((!shared && !sharingAccount) ||
2244 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2245 if (menuNodesCount == 1)
2246 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2248 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2249 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2250 [menuItem setRepresentedObject:menuNodes];
2251 [menu addItem:menuItem];
2254 if (!shared && !sharingAccount) {
2255 if (menuNodesCount == 1) {
2256 PithosNode *menuNode = [menuNodes objectAtIndex:0];
2257 if (([menuNode class] == [PithosSubdirNode class]) &&
2258 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
2259 if (clipboardNodes) {
2260 NSUInteger clipboardNodesCount = [clipboardNodes count];
2261 if (clipboardNodesCount == 0) {
2262 self.clipboardNodes = nil;
2263 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2264 if (clipboardNodesCount == 1)
2265 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2267 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2268 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2269 [menuItem setRepresentedObject:menuNode];
2270 [menu addItem:menuItem];
2280 #pragma mark Menu Actions
2282 - (void)menuNewFolder:(NSMenuItem *)sender {
2283 PithosNode *node = (PithosNode *)[sender representedObject];
2284 if ([node class] == [PithosContainerNode class]) {
2285 // Operation: Create (upload) a new root application/directory object
2286 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2287 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2288 if (operation.isCancelled) {
2292 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2293 containerName:node.pithosContainer.name
2294 subdirName:@"untitled folder"];
2295 NSString *fileName = [safeObjectName lastPathComponent];
2296 if (operation.isCancelled) {
2300 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2301 containerName:node.pithosContainer.name
2302 objectName:safeObjectName
2304 contentType:@"application/directory"
2306 contentDisposition:nil
2309 isPublic:ASIPithosObjectRequestPublicIgnore
2311 data:[NSData data]];
2312 objectRequest.delegate = self;
2313 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2314 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2315 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2316 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2317 message:messagePrefix];
2318 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2319 fileName, @"fileName",
2320 [NSArray arrayWithObject:node], @"refreshNodes",
2321 [NSNumber numberWithBool:YES], @"refresh",
2322 activity, @"activity",
2323 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2324 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2325 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2326 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2327 [NSNumber numberWithUnsignedInteger:10], @"retries",
2328 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2329 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2330 uploadNetworkQueue, @"networkQueue",
2331 @"upload", @"operationType",
2333 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2336 [uploadQueue addOperation:operation];
2337 } else if (([node class] == [PithosSubdirNode class]) &&
2338 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2339 // Operation: Create (upload) a new aplication/directory object
2340 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2341 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2342 if (operation.isCancelled) {
2346 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2347 containerName:node.pithosContainer.name
2348 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2349 NSString *fileName = [safeObjectName lastPathComponent];
2350 if (operation.isCancelled) {
2354 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2355 containerName:node.pithosContainer.name
2356 objectName:safeObjectName
2358 contentType:@"application/directory"
2360 contentDisposition:nil
2363 isPublic:ASIPithosObjectRequestPublicIgnore
2365 data:[NSData data]];
2366 objectRequest.delegate = self;
2367 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2368 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2369 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2370 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2371 message:messagePrefix];
2372 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2373 fileName, @"fileName",
2374 [NSArray arrayWithObject:node], @"refreshNodes",
2375 [NSNumber numberWithBool:YES], @"refresh",
2376 activity, @"activity",
2377 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2378 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2379 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2380 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2381 [NSNumber numberWithUnsignedInteger:10], @"retries",
2382 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2383 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2384 uploadNetworkQueue, @"networkQueue",
2385 @"upload", @"operationType",
2387 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2390 [uploadQueue addOperation:operation];
2394 - (void)menuGetInfo:(NSMenuItem *)sender {
2395 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2396 [node showPithosNodeInfo:sender];
2400 - (void)menuDelete:(NSMenuItem *)sender {
2401 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2402 if (([node class] == [PithosObjectNode class]) ||
2403 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2404 // Operation: Delete an object or subdir/ node
2405 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2406 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2407 if (operation.isCancelled) {
2411 NSString *fileName = [node.pithosObject.name lastPathComponent];
2412 if ([node.pithosObject.name hasSuffix:@"/"])
2413 fileName = [fileName stringByAppendingString:@"/"];
2414 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2415 containerName:node.pithosContainer.name
2416 objectName:node.pithosObject.name];
2417 if (operation.isCancelled) {
2421 objectRequest.delegate = self;
2422 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2423 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2424 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2425 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2426 message:messagePrefix];
2427 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2428 fileName, @"fileName",
2429 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2430 activity, @"activity",
2431 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2432 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2433 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2434 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2435 [NSNumber numberWithUnsignedInteger:10], @"retries",
2436 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2437 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2438 deleteNetworkQueue, @"networkQueue",
2439 @"delete", @"operationType",
2441 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2444 [deleteQueue addOperation:operation];
2445 } else if ([node class] == [PithosSubdirNode class]) {
2446 // Operation: Delete a subdir node and its descendants
2447 // The resulting ASIPithosObjectRequests are chained through dependencies
2448 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2449 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2450 if (operation.isCancelled) {
2454 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2455 containerName:node.pithosContainer.name
2456 objectName:node.pithosObject.name];
2457 if (!operation.isCancelled && objectRequests) {
2458 ASIPithosObjectRequest *previousObjectRequest = nil;
2459 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2460 if (operation.isCancelled) {
2464 objectRequest.delegate = self;
2465 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2466 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2467 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2468 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2469 message:messagePrefix];
2470 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2471 [NSDictionary dictionaryWithObjectsAndKeys:
2472 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2473 activity, @"activity",
2474 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2475 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2476 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2477 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2478 [NSNumber numberWithUnsignedInteger:10], @"retries",
2479 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2480 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2481 deleteNetworkQueue, @"networkQueue",
2482 @"delete", @"operationType",
2484 if (previousObjectRequest)
2485 [objectRequest addDependency:previousObjectRequest];
2486 previousObjectRequest = objectRequest;
2487 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2492 [deleteQueue addOperation:operation];
2497 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2498 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2499 if (([node class] == [PithosObjectNode class]) ||
2500 (([node class] == [PithosSubdirNode class]) &&
2501 !node.pithosObject.subdir &&
2502 [node.pithosObject.name hasSuffix:@"/"])) {
2503 // Operation: Move to trash an object or subdir/ node
2504 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2505 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2506 if (operation.isCancelled) {
2510 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2511 containerName:@"trash"
2512 objectName:node.pithosObject.name];
2513 if (!operation.isCancelled && safeObjectName) {
2514 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2515 containerName:node.pithosContainer.name
2516 objectName:node.pithosObject.name
2517 destinationContainerName:@"trash"
2518 destinationObjectName:safeObjectName
2520 if (!operation.isCancelled && objectRequest) {
2521 objectRequest.delegate = self;
2522 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2523 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2524 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2525 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2526 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2527 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2528 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2529 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2530 message:messagePrefix];
2531 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2532 [NSDictionary dictionaryWithObjectsAndKeys:
2533 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2534 activity, @"activity",
2535 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2536 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2537 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2538 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2539 [NSNumber numberWithUnsignedInteger:10], @"retries",
2540 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2541 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2542 moveNetworkQueue, @"networkQueue",
2543 @"move", @"operationType",
2545 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2550 [moveQueue addOperation:operation];
2551 } else if ([node class] == [PithosSubdirNode class]) {
2552 // Operation: Move to trash a subdir node and its descendants
2553 // The resulting ASIPithosObjectRequests are chained through dependencies
2554 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2555 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2556 if (operation.isCancelled) {
2560 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2561 containerName:@"trash"
2562 subdirName:node.pithosObject.name];
2563 if (!operation.isCancelled && safeObjectName) {
2564 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2565 containerName:node.pithosContainer.name
2566 objectName:node.pithosObject.name
2567 destinationContainerName:@"trash"
2568 destinationObjectName:safeObjectName
2570 if (!operation.isCancelled && objectRequests) {
2571 ASIPithosObjectRequest *previousObjectRequest = nil;
2572 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2573 if (operation.isCancelled) {
2577 objectRequest.delegate = self;
2578 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2579 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2580 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2581 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2582 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2583 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2584 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2585 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2586 message:messagePrefix];
2587 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2588 [NSDictionary dictionaryWithObjectsAndKeys:
2589 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2590 activity, @"activity",
2591 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2592 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2593 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2594 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2595 [NSNumber numberWithUnsignedInteger:10], @"retries",
2596 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2597 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2598 moveNetworkQueue, @"networkQueue",
2599 @"move", @"operationType",
2601 if (previousObjectRequest)
2602 [objectRequest addDependency:previousObjectRequest];
2603 previousObjectRequest = objectRequest;
2604 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2610 [moveQueue addOperation:operation];
2615 - (void)menuCut:(NSMenuItem *)sender {
2616 self.clipboardNodes = (NSArray *)[sender representedObject];
2617 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2618 self.clipboardCopy = NO;
2621 - (void)menuCopy:(NSMenuItem *)sender {
2622 self.clipboardNodes = (NSArray *)[sender representedObject];
2623 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2624 self.clipboardCopy = YES;
2627 - (void)menuPaste:(NSMenuItem *)sender {
2628 if (!clipboardNodes || ![clipboardNodes count])
2630 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2631 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2632 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2633 self.clipboardNodes = nil;
2634 self.clipboardParentNode = nil;
2635 [self moveNodes:localClipboardNodes toNode:dropNode];
2637 [self copyNodes:localClipboardNodes toNode:dropNode];
2642 #pragma mark PithosActivityFacilityDelegate
2644 - (void)activityUpdate:(NSDictionary *)info {
2645 NSString *message = [info objectForKey:@"message"];
2646 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2647 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2648 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2649 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2650 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2651 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2652 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2653 if (runningActivitiesCount && totalBytes) {
2654 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2655 [activityProgressIndicator startAnimation:self];
2657 [activityProgressIndicator setDoubleValue:1.0];
2658 [activityProgressIndicator stopAnimation:self];
2662 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2663 [activityTextField setStringValue:message];