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 #define REFRESH_TIMER_INTERVAL 5
61 @interface PithosBrowserCell : FileSystemBrowserCell {}
64 @implementation PithosBrowserCell
67 if ((self = [super init])) {
68 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
69 [self setEditable:YES];
74 - (void)setObjectValue:(id)object {
75 if ([object isKindOfClass:[PithosNode class]]) {
76 PithosNode *node = (PithosNode *)object;
77 [self setStringValue:node.displayName];
78 [self setImage:node.icon];
80 [super setObjectValue:object];
86 @interface PithosOutlineViewCell : ImageAndTextCell {}
89 @implementation PithosOutlineViewCell
91 - (void)setObjectValue:(id)object {
92 if ([object isKindOfClass:[PithosNode class]]) {
93 PithosNode *node = (PithosNode *)object;
94 [self setStringValue:node.displayName];
95 [self setImage:node.icon];
96 [self setEditable:NO];
98 [super setObjectValue:object];
104 @interface PithosBrowserController (Private)
105 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
106 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
107 - (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
110 @implementation PithosBrowserController
112 @synthesize accountNode;
113 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu;
114 @synthesize draggedNodes, draggedParentNode;
115 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
116 @synthesize activityTextField, activityProgressIndicator;
119 #pragma Object Lifecycle
122 return [super initWithWindowNibName:@"PithosBrowserController"];
125 - (void)windowDidLoad {
126 [super windowDidLoad];
127 if (browser && !browserInitialized) {
128 browserInitialized = YES;
133 - (void)initBrowser {
134 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
135 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
136 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
138 [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
139 [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
140 [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
142 [browser setCellClass:[PithosBrowserCell class]];
143 [browser setAllowsBranchSelection:YES];
144 [browser setAllowsMultipleSelection:YES];
145 [browser setAllowsEmptySelection:YES];
146 [browser setAllowsTypeSelect:YES];
147 [browser setDoubleAction:@selector(browserDoubleAction:)];
149 moveNetworkQueue = [[ASINetworkQueue alloc] init];
150 moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
151 // moveNetworkQueue.maxConcurrentOperationCount = 1;
152 copyNetworkQueue = [[ASINetworkQueue alloc] init];
153 copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
154 // copyNetworkQueue.maxConcurrentOperationCount = 1;
155 deleteNetworkQueue = [[ASINetworkQueue alloc] init];
156 deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
157 // deleteNetworkQueue.maxConcurrentOperationCount = 1;
158 uploadNetworkQueue = [[ASINetworkQueue alloc] init];
159 uploadNetworkQueue.showAccurateProgress = YES;
160 uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
161 // uploadNetworkQueue.maxConcurrentOperationCount = 1;
162 downloadNetworkQueue = [[ASINetworkQueue alloc] init];
163 downloadNetworkQueue.showAccurateProgress = YES;
164 downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
165 // downloadNetworkQueue.maxConcurrentOperationCount = 1;
167 moveQueue = [[NSOperationQueue alloc] init];
168 [moveQueue setSuspended:YES];
169 moveQueue.name = @"gr.grnet.pithos.MoveQueue";
170 // moveQueue.maxConcurrentOperationCount = 1;
171 copyQueue = [[NSOperationQueue alloc] init];
172 [copyQueue setSuspended:YES];
173 copyQueue.name = @"gr.grnet.pithos.CopyQueue";
174 // copyQueue.maxConcurrentOperationCount = 1;
175 deleteQueue = [[NSOperationQueue alloc] init];
176 [deleteQueue setSuspended:YES];
177 deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
178 // deleteQueue.maxConcurrentOperationCount = 1;
179 uploadQueue = [[NSOperationQueue alloc] init];
180 [uploadQueue setSuspended:YES];
181 uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
182 // uploadQueue.maxConcurrentOperationCount = 1;
183 downloadQueue = [[NSOperationQueue alloc] init];
184 [downloadQueue setSuspended:YES];
185 downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
186 // downloadQueue.maxConcurrentOperationCount = 1;
188 moveCallbackQueue = [[NSOperationQueue alloc] init];
189 [moveCallbackQueue setSuspended:YES];
190 moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
191 // moveCallbackQueue.maxConcurrentOperationCount = 1;
192 copyCallbackQueue = [[NSOperationQueue alloc] init];
193 [copyCallbackQueue setSuspended:YES];
194 copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
195 // copyCallbackQueue.maxConcurrentOperationCount = 1;
196 deleteCallbackQueue = [[NSOperationQueue alloc] init];
197 [deleteCallbackQueue setSuspended:YES];
198 deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
199 // deleteCallbackQueue.maxConcurrentOperationCount = 1;
200 uploadCallbackQueue = [[NSOperationQueue alloc] init];
201 [uploadCallbackQueue setSuspended:YES];
202 uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
203 // uploadCallbackQueue.maxConcurrentOperationCount = 1;
204 downloadCallbackQueue = [[NSOperationQueue alloc] init];
205 [downloadCallbackQueue setSuspended:YES];
206 downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
207 // downloadCallbackQueue.maxConcurrentOperationCount = 1;
209 [activityProgressIndicator setUsesThreadedAnimation:YES];
210 [activityProgressIndicator setMinValue:0.0];
211 [activityProgressIndicator setMaxValue:1.0];
212 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
214 self.accountNode = [[PithosAccountNode alloc] initWithPithos:pithos];
215 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
216 containersNodeChildren = [[NSMutableArray alloc] init];
217 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
218 mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
219 mySharedNode.displayName = @"shared by me";
220 mySharedNode.shared = YES;
221 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
222 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
223 othersSharedNode.displayName = @"shared with me";
224 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
226 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[PithosOutlineViewCell alloc] init]];
228 // Register for updates
229 // PithosAccountNode accountNode updates outlineView container nodes
230 [[NSNotificationCenter defaultCenter] addObserver:self
231 selector:@selector(pithosAccountNodeChildrenUpdated:)
232 name:@"PithosNodeChildrenUpdated"
234 // PithosNode updates browser nodes
235 [[NSNotificationCenter defaultCenter] addObserver:self
236 selector:@selector(pithosNodeChildrenUpdated:)
237 name:@"PithosNodeChildrenUpdated"
239 // Request for browser refresh
240 [[NSNotificationCenter defaultCenter] addObserver:self
241 selector:@selector(pithosBrowserRefreshNeeded:)
242 name:@"PithosBrowserRefreshNeeeded"
246 - (void)resetBrowser {
247 @synchronized(self) {
252 [refreshTimer invalidate];
254 [moveNetworkQueue reset];
255 [copyNetworkQueue reset];
256 [deleteNetworkQueue reset];
257 [uploadNetworkQueue reset];
258 [downloadNetworkQueue reset];
260 [moveQueue cancelAllOperations];
261 [moveQueue setSuspended:YES];
262 [copyQueue cancelAllOperations];
263 [copyQueue setSuspended:YES];
264 [deleteQueue cancelAllOperations];
265 [deleteQueue setSuspended:YES];
266 [uploadQueue cancelAllOperations];
267 [uploadQueue setSuspended:YES];
268 [downloadQueue cancelAllOperations];
269 [downloadQueue setSuspended:YES];
271 [moveCallbackQueue cancelAllOperations];
272 [moveCallbackQueue setSuspended:YES];
273 [copyCallbackQueue cancelAllOperations];
274 [copyCallbackQueue setSuspended:YES];
275 [deleteCallbackQueue cancelAllOperations];
276 [deleteCallbackQueue setSuspended:YES];
277 [uploadCallbackQueue cancelAllOperations];
278 [uploadCallbackQueue setSuspended:YES];
279 [downloadCallbackQueue cancelAllOperations];
280 [downloadCallbackQueue setSuspended:YES];
283 [browser loadColumnZero];
284 [containersNodeChildren removeAllObjects];
285 [outlineView reloadData];
286 // Expand the folder outline view
287 [outlineView expandItem:nil expandChildren:YES];
288 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
290 activityFacility.delegate = nil;
291 [activityProgressIndicator setDoubleValue:1.0];
292 [activityProgressIndicator stopAnimation:self];
294 @synchronized(self) {
299 - (void)startBrowser {
300 @synchronized(self) {
305 // In the improbable case of leftover operations
306 [moveNetworkQueue reset];
307 [copyNetworkQueue reset];
308 [deleteNetworkQueue reset];
309 [uploadNetworkQueue reset];
310 [downloadNetworkQueue reset];
311 [moveQueue cancelAllOperations];
312 [copyQueue cancelAllOperations];
313 [deleteQueue cancelAllOperations];
314 [uploadQueue cancelAllOperations];
315 [downloadQueue cancelAllOperations];
316 [moveCallbackQueue cancelAllOperations];
317 [copyCallbackQueue cancelAllOperations];
318 [deleteCallbackQueue cancelAllOperations];
319 [uploadCallbackQueue cancelAllOperations];
320 [downloadCallbackQueue cancelAllOperations];
322 [moveNetworkQueue go];
323 [copyNetworkQueue go];
324 [deleteNetworkQueue go];
325 [uploadNetworkQueue go];
326 [downloadNetworkQueue go];
327 [moveQueue setSuspended:NO];
328 [copyQueue setSuspended:NO];
329 [deleteQueue setSuspended:NO];
330 [uploadQueue setSuspended:NO];
331 [downloadQueue setSuspended:NO];
332 [moveCallbackQueue setSuspended:NO];
333 [copyCallbackQueue setSuspended:NO];
334 [deleteCallbackQueue setSuspended:NO];
335 [uploadCallbackQueue setSuspended:NO];
336 [downloadCallbackQueue setSuspended:NO];
338 accountNode.pithos = pithos;
339 [accountNode forceRefresh];
340 mySharedNode.pithos = pithos;
341 [mySharedNode forceRefresh];
342 othersSharedNode.pithos = pithos;
343 [othersSharedNode forceRefresh];
345 // [activityFacility reset];
346 activityFacility.delegate = self;
348 refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
350 selector:@selector(forceRefresh:)
353 @synchronized(self) {
358 - (BOOL)operationsPending {
359 return ([moveNetworkQueue operationCount] ||
360 [copyNetworkQueue operationCount] ||
361 [deleteNetworkQueue operationCount] ||
362 [uploadNetworkQueue operationCount] ||
363 [downloadNetworkQueue operationCount] ||
364 [moveQueue operationCount] ||
365 [copyQueue operationCount] ||
366 [deleteQueue operationCount] ||
367 [uploadQueue operationCount] ||
368 [downloadQueue operationCount] ||
369 [moveCallbackQueue operationCount] ||
370 [copyCallbackQueue operationCount] ||
371 [deleteCallbackQueue operationCount] ||
372 [uploadCallbackQueue operationCount] ||
373 [downloadCallbackQueue operationCount]);
377 [[NSNotificationCenter defaultCenter] removeObserver:self];
381 - (void)setPithos:(ASIPithos *)aPithos {
383 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
384 ![aPithos.authToken isEqualToString:pithos.authToken] ||
385 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
386 ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
398 #pragma mark Observers
400 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
401 PithosNode *node = (PithosNode *)[notification object];
402 if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
404 DLog(@"pithosNodeChildrenUpdated:%@", node.url);
405 NSInteger lastColumn = [browser lastColumn];
406 for (NSInteger column = lastColumn; column >= 0; column--) {
407 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
408 [browser reloadColumn:column];
414 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
415 BOOL containerPithosFound = NO;
416 BOOL containerTrashFound = NO;
417 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
418 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
419 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
420 [removedContainersNodeChildren addIndex:i];
422 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
423 for (PithosContainerNode *containerNode in accountNode.children) {
424 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
425 if (![containersNodeChildren containsObject:containerNode])
426 [containersNodeChildren insertObject:containerNode atIndex:0];
427 containerPithosFound = YES;
428 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
429 NSUInteger insertIndex = 1;
430 if (!containerPithosFound)
432 if (![containersNodeChildren containsObject:containerNode])
433 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
434 containerTrashFound = YES;
435 } else if (![containersNodeChildren containsObject:containerNode]) {
436 [containersNodeChildren addObject:containerNode];
439 BOOL refreshAccountNode = NO;
440 if (!containerPithosFound) {
441 // Create pithos node
442 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
443 containerName:@"pithos"];
444 [PithosUtilities startAndWaitForRequest:containerRequest];
445 if ([containerRequest error]) {
446 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
448 refreshAccountNode = YES;
451 if (!containerTrashFound) {
453 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
454 containerName:@"trash"];
455 [PithosUtilities startAndWaitForRequest:containerRequest];
456 if ([containerRequest error]) {
457 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
459 refreshAccountNode = YES;
463 if (refreshAccountNode)
464 [accountNode refresh];
466 [outlineView reloadData];
468 // Expand the folder outline view
469 [outlineView expandItem:nil expandChildren:YES];
471 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
472 rootNode = [containersNodeChildren objectAtIndex:0];
473 [browser loadColumnZero];
480 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
487 - (IBAction)forceRefresh:(id)sender {
491 [accountNode forceRefresh];
492 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
493 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
494 node.forcedRefresh = YES;
495 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
497 [browser validateVisibleColumns];
500 - (IBAction)refresh:(id)sender {
503 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
504 [self forceRefresh:sender];
507 [accountNode refresh];
508 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
509 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
511 [browser validateVisibleColumns];
516 #pragma mark NSBrowserDelegate
518 - (id)rootItemForBrowser:(NSBrowser *)browser {
522 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
523 PithosNode *node = (PithosNode *)item;
524 return node.children.count;
527 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
528 PithosNode *node = (PithosNode *)item;
529 return [node.children objectAtIndex:index];
532 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
533 PithosNode *node = (PithosNode *)item;
534 return node.isLeafItem;
537 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
538 PithosNode *node = (PithosNode *)item;
542 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
543 if (sharedPreviewController == nil)
544 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
545 return sharedPreviewController;
548 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
549 // if (!forUserResize) {
550 // id item = [browser parentForItemsInColumn:columnIndex];
551 // if ([self browser:browser isLeafItem:item]) {
552 // suggestedWidth = 200;
555 // return suggestedWidth;
558 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
564 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
565 PithosNode *node = (PithosNode *)item;
566 if (node.shared || node.sharingAccount ||
567 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
573 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
575 PithosNode *node = (PithosNode *)item;
576 NSString *newName = (NSString *)object;
577 NSUInteger newNameLength = [newName length];
578 NSRange firstSlashRange = [newName rangeOfString:@"/"];
579 if ((newNameLength == 0) ||
580 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
581 ([newName isEqualToString:node.displayName])) {
584 if (([node class] == [PithosObjectNode class]) ||
585 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
586 // Operation: Rename (move) an object or subdir/ node
587 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
589 if (operation.isCancelled)
591 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
592 if ([newName hasSuffix:@"/"])
593 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
594 NSError *error = nil;
596 if ([PithosUtilities objectExistsAtPithos:pithos
597 containerName:node.pithosContainer.name
598 objectName:destinationObjectName
600 isDirectory:&isDirectory
601 sharingAccount:nil]) {
602 dispatch_async(dispatch_get_main_queue(), ^{
603 NSAlert *alert = [[NSAlert alloc] init];
604 [alert setMessageText:@"Name Taken"];
605 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
606 [alert addButtonWithTitle:@"OK"];
613 if (operation.isCancelled)
615 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
616 containerName:node.pithosContainer.name
617 objectName:node.pithosObject.name
618 destinationContainerName:node.pithosContainer.name
619 destinationObjectName:destinationObjectName
621 if (!operation.isCancelled && objectRequest) {
622 objectRequest.delegate = self;
623 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
624 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
625 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
626 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
627 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
628 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
629 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
630 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
631 message:messagePrefix];
632 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
633 [NSDictionary dictionaryWithObjectsAndKeys:
634 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
635 [NSNumber numberWithBool:YES], @"refresh",
636 activity, @"activity",
637 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
638 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
639 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
640 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
641 [NSNumber numberWithUnsignedInteger:10], @"retries",
642 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
643 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
644 moveNetworkQueue, @"networkQueue",
645 @"move", @"operationType",
647 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
651 [moveQueue addOperation:operation];
652 } else if ([node class] == [PithosSubdirNode class]) {
653 if (firstSlashRange.length == 1)
655 // Operation: Rename (move) a subdir node and its descendants
656 // The resulting ASIPithosObjectRequests are chained through dependencies
657 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
659 if (operation.isCancelled)
661 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
662 NSError *error = nil;
664 if ([PithosUtilities objectExistsAtPithos:pithos
665 containerName:node.pithosContainer.name
666 objectName:destinationObjectName
668 isDirectory:&isDirectory
669 sharingAccount:nil]) {
670 dispatch_async(dispatch_get_main_queue(), ^{
671 NSAlert *alert = [[NSAlert alloc] init];
672 [alert setMessageText:@"Name Taken"];
673 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
674 [alert addButtonWithTitle:@"OK"];
681 if (operation.isCancelled)
683 if (node.pithosObject.subdir)
684 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
685 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
686 containerName:node.pithosContainer.name
687 objectName:node.pithosObject.name
688 destinationContainerName:node.pithosContainer.name
689 destinationObjectName:destinationObjectName
691 if (!operation.isCancelled && objectRequests) {
692 ASIPithosObjectRequest *previousObjectRequest = nil;
693 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
694 if (operation.isCancelled)
696 objectRequest.delegate = self;
697 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
698 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
699 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
700 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
701 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
702 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
703 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
704 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
705 message:messagePrefix];
706 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
707 [NSDictionary dictionaryWithObjectsAndKeys:
708 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
709 [NSNumber numberWithBool:YES], @"refresh",
710 activity, @"activity",
711 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
712 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
713 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
714 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
715 [NSNumber numberWithUnsignedInteger:10], @"retries",
716 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
717 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
718 moveNetworkQueue, @"networkQueue",
719 @"move", @"operationType",
721 if (previousObjectRequest)
722 [objectRequest addDependency:previousObjectRequest];
723 previousObjectRequest = objectRequest;
724 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
729 [moveQueue addOperation:operation];
733 #pragma mark Drag and Drop source
735 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
736 withEvent:(NSEvent *)event {
737 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
738 __block BOOL result = YES;
739 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
740 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
741 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
749 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
750 toPasteboard:(NSPasteboard *)pasteboard {
751 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
752 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
753 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
754 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
755 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
756 [propertyList addObject:[node.pithosObject.name pathExtension]];
757 [nodes addObject:node];
760 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
761 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
762 self.draggedNodes = nodes;
763 self.draggedParentNode = [browser parentForItemsInColumn:column];
767 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
768 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
769 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
770 for (PithosNode *node in draggedNodes) {
771 [names addObject:node.displayName];
772 // If the node is a subdir ask if the whole tree should be downloaded
773 if ([node class] == [PithosSubdirNode class]) {
774 NSAlert *alert = [[NSAlert alloc] init];
775 [alert setMessageText:@"Download directory"];
776 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
777 [alert addButtonWithTitle:@"OK"];
778 [alert addButtonWithTitle:@"Cancel"];
779 NSInteger choice = [alert runModal];
780 if (choice == NSAlertFirstButtonReturn)
781 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
783 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
789 #pragma mark Drag and Drop destination
791 - (NSDragOperation)browser:aBrowser
792 validateDrop:(id<NSDraggingInfo>)info
793 proposedRow:(NSInteger *)row
794 column:(NSInteger *)column
795 dropOperation:(NSBrowserDropOperation *)dropOperation {
796 NSDragOperation result = NSDragOperationNone;
797 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
798 // For a drop above, the drop is redirected to the parent item
799 if (*dropOperation == NSBrowserDropAbove)
801 // Only allow dropping in folders
803 PithosNode *dropNode;
805 // Check if the node is not a folder and if so redirect to the parent item
806 dropNode = [browser itemAtRow:*row inColumn:*column];
807 if ([dropNode class] == [PithosObjectNode class])
811 dropNode = [browser parentForItemsInColumn:*column];
813 if (!dropNode.shared &&
814 (!dropNode.sharingAccount ||
815 ([dropNode class] == [PithosSubdirNode class]) ||
816 ([dropNode class] == [PithosContainerNode class]))) {
817 *dropOperation = NSBrowserDropOn;
818 result = NSDragOperationCopy;
821 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
822 // For a drop above, the drop is redirected to the parent item
823 if (*dropOperation == NSBrowserDropAbove)
825 // Only allow dropping in folders
827 PithosNode *dropNode;
829 // Check if the node is not a folder and if so redirect to the parent item
830 dropNode = [browser itemAtRow:*row inColumn:*column];
831 if ([dropNode class] == [PithosObjectNode class])
835 dropNode = [browser parentForItemsInColumn:*column];
837 if (!dropNode.shared && !dropNode.sharingAccount) {
838 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
839 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
840 if ((([dropNode class] == [PithosContainerNode class]) ||
841 dropNode.pithosObject.subdir ||
842 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
843 ![dropNode isEqualTo:draggedParentNode]) {
844 // ![dropNode isEqualTo:draggedParentNode] &&
845 // ![draggedNodes containsObject:dropNode]) {
846 result = NSDragOperationMove;
848 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
849 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
850 if (([dropNode class] == [PithosContainerNode class]) ||
851 dropNode.pithosObject.subdir ||
852 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
853 result = NSDragOperationCopy;
862 - (BOOL)browser:(NSBrowser *)aBrowser
863 acceptDrop:(id<NSDraggingInfo>)info
865 column:(NSInteger)column
866 dropOperation:(NSBrowserDropOperation)dropOperation {
867 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
868 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
869 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
870 if ((column != -1) && (filenames != nil)) {
873 node = [browser itemAtRow:row inColumn:column];
875 node = [browser parentForItemsInColumn:column];
876 DLog(@"drag in node: %@", node.url);
877 return [self uploadFiles:filenames toNode:node];
879 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
880 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
881 if ((column != -1) && (draggedNodes != nil)) {
884 node = [browser itemAtRow:row inColumn:column];
886 node = [browser parentForItemsInColumn:column];
887 DLog(@"drag local node: %@", node.url);
888 if ([info draggingSourceOperationMask] & NSDragOperationMove)
889 return [self moveNodes:draggedNodes toNode:node];
890 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
891 return [self cpyNodes:draggedNodes toNode:node];
898 #pragma mark NSBrowser Actions
900 - (void)browserDoubleAction:(id)sender {
901 NSInteger column = [browser clickedColumn];
902 NSInteger row = [browser clickedRow];
903 if ((column == -1) || (row == -1))
905 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
906 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
907 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
908 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
909 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
910 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
913 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
915 NSMenuItem *menuItem = [[NSMenuItem alloc] init];
916 menuItem.representedObject = menuNodes;
917 [self menuDownload:menuItem];
921 #pragma mark Drag and Drop methods
923 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
924 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
925 if ([node class] == [PithosSubdirNode class]) {
926 // XXX newFilename and version are ignored in the case of a subdir node for now
927 // Operation: Download a subdir node and its descendants
928 // The resulting ASIPithosObjectRequests are chained through dependencies
929 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
931 if (operation.isCancelled)
933 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
934 containerName:node.pithosContainer.name
935 objectName:node.pithosObject.name
937 checkIfExists:checkIfExists
938 sharingAccount:node.sharingAccount];
939 if (!operation.isCancelled && objectRequests) {
940 ASIPithosObjectRequest *previousObjectRequest = nil;
941 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
942 if (operation.isCancelled)
944 objectRequest.delegate = self;
945 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
946 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
947 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
948 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
949 message:[messagePrefix stringByAppendingString:@" (0%)"]
950 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
952 dispatch_async(dispatch_get_main_queue(), ^{
953 [activityFacility updateActivity:activity withMessage:activity.message];
955 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
956 [NSDictionary dictionaryWithObjectsAndKeys:
957 activity, @"activity",
958 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
959 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
960 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
961 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
962 [NSNumber numberWithUnsignedInteger:10], @"retries",
963 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
964 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
965 downloadNetworkQueue, @"networkQueue",
966 @"download", @"operationType",
968 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
969 [activityFacility updateActivity:activity
970 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
971 totalBytes:activity.totalBytes
972 currentBytes:(activity.currentBytes + size)];
974 if (previousObjectRequest)
975 [objectRequest addDependency:previousObjectRequest];
976 previousObjectRequest = objectRequest;
977 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
982 [downloadQueue addOperation:operation];
983 } else if ([node class] == [PithosObjectNode class]) {
984 // Operation: Download an object node
985 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
987 if (operation.isCancelled)
989 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
990 containerName:node.pithosContainer.name
991 objectName:node.pithosObject.name
994 withNewFileName:newFileName
995 checkIfExists:checkIfExists
996 sharingAccount:node.sharingAccount];
997 if (!operation.isCancelled && objectRequest) {
998 objectRequest.delegate = self;
999 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1000 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1001 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1002 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1003 message:[messagePrefix stringByAppendingString:@" (0%)"]
1004 totalBytes:node.pithosObject.bytes
1006 dispatch_async(dispatch_get_main_queue(), ^{
1007 [activityFacility updateActivity:activity withMessage:activity.message];
1009 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1010 [NSDictionary dictionaryWithObjectsAndKeys:
1011 activity, @"activity",
1012 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1013 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1014 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1015 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1016 [NSNumber numberWithUnsignedInteger:10], @"retries",
1017 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1018 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1019 downloadNetworkQueue, @"networkQueue",
1020 @"download", @"operationType",
1022 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1023 [activityFacility updateActivity:activity
1024 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1025 totalBytes:activity.totalBytes
1026 currentBytes:(activity.currentBytes + size)];
1028 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1032 [downloadQueue addOperation:operation];
1036 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1037 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1039 NSFileManager *fileManager = [NSFileManager defaultManager];
1040 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1041 NSString *objectNamePrefix;
1042 if ([destinationNode class] == [PithosSubdirNode class])
1043 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1045 objectNamePrefix = [NSString string];
1046 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1047 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1048 containerName:containerName];
1049 [PithosUtilities startAndWaitForRequest:containerRequest];
1050 if ([containerRequest error]) {
1051 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1053 } else if (containerRequest.responseStatusCode != 204) {
1054 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1057 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1058 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1060 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1061 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1063 for (NSString *filePath in filenames) {
1065 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1068 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1069 precomposedStringWithCanonicalMapping];
1070 // Operation: Upload a local file
1071 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1073 if (operation.isCancelled)
1075 NSError *error = nil;
1076 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1077 if (contentType == nil)
1078 contentType = @"application/octet-stream";
1081 DLog(@"contentType detection error: %@", error);
1083 NSArray *hashes = nil;
1084 if (operation.isCancelled)
1086 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1087 containerName:containerName
1088 objectName:objectName
1089 contentType:contentType
1095 sharingAccount:destinationNode.sharingAccount];
1096 if (!operation.isCancelled && objectRequest) {
1097 objectRequest.delegate = self;
1098 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1099 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1100 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1101 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1102 message:[messagePrefix stringByAppendingString:@" (0%)"]
1103 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1105 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1106 [NSDictionary dictionaryWithObjectsAndKeys:
1107 containerName, @"containerName",
1108 objectName, @"objectName",
1109 contentType, @"contentType",
1110 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1111 blockHash, @"blockHash",
1112 filePath, @"filePath",
1114 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1115 [NSNumber numberWithBool:YES], @"refresh",
1116 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1117 activity, @"activity",
1118 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1119 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1120 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1121 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1122 [NSNumber numberWithUnsignedInteger:10], @"retries",
1123 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1124 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1125 uploadNetworkQueue, @"networkQueue",
1126 @"upload", @"operationType",
1128 if (destinationNode.sharingAccount)
1129 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1130 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1134 [uploadQueue addOperation:operation];
1136 // Upload directory, confirm first
1137 NSAlert *alert = [[NSAlert alloc] init];
1138 [alert setMessageText:@"Upload directory"];
1139 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1140 [alert addButtonWithTitle:@"OK"];
1141 [alert addButtonWithTitle:@"Cancel"];
1142 NSInteger choice = [alert runModal];
1143 if (choice == NSAlertFirstButtonReturn) {
1144 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1145 precomposedStringWithCanonicalMapping];
1146 // Operation: Upload a local directory and its descendants
1147 // The resulting ASIPithosObjectRequests are chained through dependencies
1148 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1150 if (operation.isCancelled)
1152 NSMutableArray *objectNames = nil;
1153 NSMutableArray *contentTypes = nil;
1154 NSMutableArray *filePaths = nil;
1155 NSMutableArray *hashesArrays = nil;
1156 NSMutableArray *directoryObjectRequests = nil;
1157 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1158 containerName:containerName
1159 objectName:objectName
1162 forDirectory:filePath
1164 objectNames:&objectNames
1165 contentTypes:&contentTypes
1166 filePaths:&filePaths
1167 hashesArrays:&hashesArrays
1168 directoryObjectRequests:&directoryObjectRequests
1169 sharingAccount:destinationNode.sharingAccount];
1170 if (operation.isCancelled)
1172 ASIPithosObjectRequest *previousObjectRequest = nil;
1173 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1174 if (operation.isCancelled)
1176 objectRequest.delegate = self;
1177 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1178 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1179 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1180 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1181 message:messagePrefix];
1182 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1183 [NSDictionary dictionaryWithObjectsAndKeys:
1184 [NSNumber numberWithBool:YES], @"refresh",
1185 activity, @"activity",
1186 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1187 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1188 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1189 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1190 [NSNumber numberWithUnsignedInteger:10], @"retries",
1191 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1192 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1193 uploadNetworkQueue, @"networkQueue",
1194 @"upload", @"operationType",
1196 if (previousObjectRequest)
1197 [objectRequest addDependency:previousObjectRequest];
1198 previousObjectRequest = objectRequest;
1199 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1201 if (!operation.isCancelled && objectRequests) {
1202 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1203 if (operation.isCancelled)
1205 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1206 objectRequest.delegate = self;
1207 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1208 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1209 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1210 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1211 message:[messagePrefix stringByAppendingString:@" (0%)"]
1212 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1214 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1215 [NSDictionary dictionaryWithObjectsAndKeys:
1216 containerName, @"containerName",
1217 [objectNames objectAtIndex:i], @"objectName",
1218 [contentTypes objectAtIndex:i], @"contentType",
1219 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1220 blockHash, @"blockHash",
1221 [filePaths objectAtIndex:i], @"filePath",
1222 [hashesArrays objectAtIndex:i], @"hashes",
1223 [NSNumber numberWithBool:YES], @"refresh",
1224 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1225 activity, @"activity",
1226 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1227 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1228 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1229 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1230 [NSNumber numberWithUnsignedInteger:10], @"retries",
1231 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1232 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1233 uploadNetworkQueue, @"networkQueue",
1234 @"upload", @"operationType",
1236 if (destinationNode.sharingAccount)
1237 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1238 if (previousObjectRequest)
1239 [objectRequest addDependency:previousObjectRequest];
1240 previousObjectRequest = objectRequest;
1241 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1246 [uploadQueue addOperation:operation];
1254 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1255 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1256 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1258 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1259 NSString *objectNamePrefix;
1260 if ([destinationNode class] == [PithosSubdirNode class])
1261 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1263 objectNamePrefix = [NSString string];
1265 for (PithosNode *node in nodes) {
1266 if (([node class] == [PithosObjectNode class]) ||
1267 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1268 // Operation: Move an object or subdir/ node
1269 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1271 if (operation.isCancelled)
1273 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1274 if ([node.pithosObject.name hasSuffix:@"/"])
1275 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1276 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1277 containerName:node.pithosContainer.name
1278 objectName:node.pithosObject.name
1279 destinationContainerName:containerName
1280 destinationObjectName:destinationObjectName
1282 if (!operation.isCancelled && objectRequest) {
1283 objectRequest.delegate = self;
1284 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1285 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1286 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1287 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1288 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1289 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1290 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1291 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1292 message:messagePrefix];
1293 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1294 [NSDictionary dictionaryWithObjectsAndKeys:
1295 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1296 activity, @"activity",
1297 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1298 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1299 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1300 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1301 [NSNumber numberWithUnsignedInteger:10], @"retries",
1302 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1303 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1304 moveNetworkQueue, @"networkQueue",
1305 @"move", @"operationType",
1307 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1311 [moveQueue addOperation:operation];
1312 } else if ([node class] == [PithosSubdirNode class]) {
1313 // Operation: Move a subdir node and its descendants
1314 // The resulting ASIPithosObjectRequests are chained through dependencies
1315 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1317 if (operation.isCancelled)
1319 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1320 if (node.pithosObject.subdir)
1321 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1322 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1323 containerName:node.pithosContainer.name
1324 objectName:node.pithosObject.name
1325 destinationContainerName:containerName
1326 destinationObjectName:destinationObjectName
1328 if (!operation.isCancelled && objectRequests) {
1329 ASIPithosObjectRequest *previousObjectRequest = nil;
1330 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1331 if (operation.isCancelled)
1333 objectRequest.delegate = self;
1334 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1335 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1336 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1337 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1338 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1339 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1340 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1341 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1342 message:messagePrefix];
1343 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1344 [NSDictionary dictionaryWithObjectsAndKeys:
1345 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1346 [NSNumber numberWithBool:YES], @"refresh",
1347 activity, @"activity",
1348 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1349 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1350 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1351 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1352 [NSNumber numberWithUnsignedInteger:10], @"retries",
1353 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1354 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1355 moveNetworkQueue, @"networkQueue",
1356 @"move", @"operationType",
1358 if (previousObjectRequest)
1359 [objectRequest addDependency:previousObjectRequest];
1360 previousObjectRequest = objectRequest;
1361 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1366 [moveQueue addOperation:operation];
1372 - (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1373 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1374 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1376 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1377 NSString *objectNamePrefix;
1378 if ([destinationNode class] == [PithosSubdirNode class])
1379 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1381 objectNamePrefix = [NSString string];
1383 for (PithosNode *node in nodes) {
1384 if (([node class] == [PithosObjectNode class]) ||
1385 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1386 // Operation: Copy an object or subdir/ node
1387 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1389 if (operation.isCancelled)
1391 NSString *destinationObjectName;
1392 if (![destinationNode isEqualTo:node.parent]) {
1393 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1394 if ([node.pithosObject.name hasSuffix:@"/"])
1395 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1397 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1398 containerName:containerName
1399 objectName:node.pithosObject.name];
1401 if (operation.isCancelled)
1403 ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithos
1404 containerName:node.pithosContainer.name
1405 objectName:node.pithosObject.name
1406 destinationContainerName:containerName
1407 destinationObjectName:destinationObjectName
1409 sharingAccount:node.sharingAccount];
1410 if (!operation.isCancelled && objectRequest) {
1411 objectRequest.delegate = self;
1412 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1413 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1414 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1415 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1416 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1417 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1418 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1419 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1420 message:messagePrefix];
1421 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1422 [NSDictionary dictionaryWithObjectsAndKeys:
1423 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1424 activity, @"activity",
1425 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1426 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1427 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1428 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1429 [NSNumber numberWithUnsignedInteger:10], @"retries",
1430 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1431 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1432 copyNetworkQueue, @"networkQueue",
1433 @"copy", @"operationType",
1435 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1439 [copyQueue addOperation:operation];
1440 } else if ([node class] == [PithosSubdirNode class]) {
1441 // Operation: Copy a subdir node and its descendants
1442 // The resulting ASIPithosObjectRequests are chained through dependencies
1443 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1445 if (operation.isCancelled)
1447 NSString *destinationObjectName;
1448 if (![destinationNode isEqualTo:node.parent]) {
1449 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1450 if (node.pithosObject.subdir)
1451 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1453 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1454 containerName:containerName
1455 subdirName:node.pithosObject.name];
1457 if (operation.isCancelled)
1459 NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithos
1460 containerName:node.pithosContainer.name
1461 objectName:node.pithosObject.name
1462 destinationContainerName:containerName
1463 destinationObjectName:destinationObjectName
1465 sharingAccount:node.sharingAccount];
1466 if (!operation.isCancelled && objectRequests) {
1467 ASIPithosObjectRequest *previousObjectRequest = nil;
1468 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1469 if (operation.isCancelled)
1471 objectRequest.delegate = self;
1472 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1473 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1474 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1475 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1476 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1477 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1478 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1479 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1480 message:messagePrefix];
1481 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1482 [NSDictionary dictionaryWithObjectsAndKeys:
1483 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1484 activity, @"activity",
1485 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1486 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1487 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1488 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1489 [NSNumber numberWithUnsignedInteger:10], @"retries",
1490 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1491 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1492 copyNetworkQueue, @"networkQueue",
1493 @"copy", @"operationType",
1495 if (previousObjectRequest)
1496 [objectRequest addDependency:previousObjectRequest];
1497 previousObjectRequest = objectRequest;
1498 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1503 [copyQueue addOperation:operation];
1510 #pragma mark ASIHTTPRequestDelegate
1512 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1513 NSOperationQueue *callbackQueue;
1514 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1515 if ([operationType isEqualToString:@"move"])
1516 callbackQueue = moveCallbackQueue;
1517 else if ([operationType isEqualToString:@"copy"])
1518 callbackQueue = copyCallbackQueue;
1519 else if ([operationType isEqualToString:@"delete"])
1520 callbackQueue = deleteCallbackQueue;
1521 else if ([operationType isEqualToString:@"upload"])
1522 callbackQueue = uploadCallbackQueue;
1523 else if ([operationType isEqualToString:@"download"])
1524 callbackQueue = downloadCallbackQueue;
1526 dispatch_async(dispatch_get_main_queue(), ^{
1527 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1528 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1532 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1533 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1534 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1536 operation.completionBlock = ^{
1538 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1539 dispatch_async(dispatch_get_main_queue(), ^{
1540 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1541 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1546 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1547 [callbackQueue addOperation:operation];
1550 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1551 if (request.isCancelled) {
1552 // Request has been cancelled
1553 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1554 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1555 withObject:request];
1557 NSOperationQueue *callbackQueue;
1558 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1559 if ([operationType isEqualToString:@"move"])
1560 callbackQueue = moveCallbackQueue;
1561 else if ([operationType isEqualToString:@"copy"])
1562 callbackQueue = copyCallbackQueue;
1563 else if ([operationType isEqualToString:@"delete"])
1564 callbackQueue = deleteCallbackQueue;
1565 else if ([operationType isEqualToString:@"upload"])
1566 callbackQueue = uploadCallbackQueue;
1567 else if ([operationType isEqualToString:@"download"])
1568 callbackQueue = downloadCallbackQueue;
1570 dispatch_async(dispatch_get_main_queue(), ^{
1571 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1572 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1576 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1577 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1578 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1580 operation.completionBlock = ^{
1582 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1583 dispatch_async(dispatch_get_main_queue(), ^{
1584 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1585 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1590 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1591 [callbackQueue addOperation:operation];
1595 - (void)requestFailed:(ASIPithosRequest *)request {
1597 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1598 DLog(@"Request failed: %@", request.url);
1599 if (operation.isCancelled)
1601 if (request.isCancelled) {
1602 dispatch_async(dispatch_get_main_queue(), ^{
1603 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1604 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1608 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1610 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1611 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1612 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1613 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1615 dispatch_async(dispatch_get_main_queue(), ^{
1616 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1617 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1619 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1620 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1622 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1627 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1629 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1630 DLog(@"Download finished: %@", objectRequest.url);
1631 if (operation.isCancelled) {
1632 [self requestFailed:objectRequest];
1633 } else if (objectRequest.responseStatusCode == 200) {
1634 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1635 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1636 NSUInteger totalBytes = activity.totalBytes;
1638 // XXX change contentLength to objectContentLength if it is fixed in the server
1639 if ([objectRequest contentLength] == 0) {
1640 // The check above was:
1641 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1642 // I checked for directory content types in order not to create a file in place of a directory,
1643 // but this callback method is not called in the case of a directory download.
1644 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1645 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1646 DLog(@"Downloaded 0 bytes");
1647 NSFileManager *fileManager = [NSFileManager defaultManager];
1648 if (![fileManager fileExistsAtPath:filePath]) {
1649 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1650 dispatch_async(dispatch_get_main_queue(), ^{
1651 NSAlert *alert = [[NSAlert alloc] init];
1652 [alert setMessageText:@"Create File Error"];
1653 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1654 [alert addButtonWithTitle:@"OK"];
1661 NSUInteger currentBytes = [objectRequest objectContentLength];
1662 if (currentBytes == 0)
1663 currentBytes = totalBytes;
1664 dispatch_async(dispatch_get_main_queue(), ^{
1665 [activityFacility endActivity:activity
1666 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1667 totalBytes:totalBytes
1668 currentBytes:currentBytes];
1671 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1672 [self requestFailed:objectRequest];
1677 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1679 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1680 DLog(@"Upload directory object finished: %@", objectRequest.url);
1681 if (operation.isCancelled) {
1682 [self requestFailed:objectRequest];
1683 } else if (objectRequest.responseStatusCode == 201) {
1684 DLog(@"Directory object created: %@", objectRequest.url);
1685 dispatch_async(dispatch_get_main_queue(), ^{
1686 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1687 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1688 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1689 [node forceRefresh];
1691 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1694 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1695 [self forceRefresh:self];
1696 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1697 [self refresh:self];
1700 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1701 [self requestFailed:objectRequest];
1706 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1708 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1709 DLog(@"Upload using hashmap finished: %@", objectRequest.url);
1710 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1711 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1712 NSUInteger totalBytes = activity.totalBytes;
1713 NSUInteger currentBytes = activity.currentBytes;
1714 if (operation.isCancelled) {
1715 [self requestFailed:objectRequest];
1716 } else if (objectRequest.responseStatusCode == 201) {
1717 DLog(@"Object created: %@", objectRequest.url);
1718 dispatch_async(dispatch_get_main_queue(), ^{
1719 [activityFacility endActivity:activity
1720 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1721 totalBytes:totalBytes
1722 currentBytes:totalBytes];
1723 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1724 [node forceRefresh];
1726 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1729 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1730 [self forceRefresh:self];
1731 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1732 [self refresh:self];
1734 } else if (objectRequest.responseStatusCode == 409) {
1735 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1736 if (iteration == 0) {
1737 DLog(@"Upload iteration limit reached: %@", objectRequest.url);
1738 dispatch_async(dispatch_get_main_queue(), ^{
1739 [activityFacility endActivity:activity
1740 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1741 NSAlert *alert = [[NSAlert alloc] init];
1742 [alert setMessageText:@"Upload Timeout"];
1743 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1744 [objectRequest.userInfo objectForKey:@"objectName"]]];
1745 [alert addButtonWithTitle:@"OK"];
1750 DLog(@"object is missing hashes: %@", objectRequest.url);
1751 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1752 withMissingHashes:[objectRequest hashes]];
1753 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1754 if (totalBytes >= [missingBlocks count]*blockSize)
1755 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1756 dispatch_async(dispatch_get_main_queue(), ^{
1757 [activityFacility updateActivity:activity
1758 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1759 totalBytes:totalBytes
1760 currentBytes:currentBytes];
1762 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1763 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1764 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1766 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1767 missingBlockIndex:missingBlockIndex
1768 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1769 newContainerRequest.delegate = self;
1770 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1771 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1772 newContainerRequest.userInfo = objectRequest.userInfo;
1773 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1774 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1775 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1776 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1777 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1778 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1779 [activityFacility updateActivity:activity
1780 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1781 totalBytes:activity.totalBytes
1782 currentBytes:(activity.currentBytes + size)];
1784 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1786 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1787 [self requestFailed:objectRequest];
1792 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1794 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1795 DLog(@"Upload of missing block finished: %@", containerRequest.url);
1796 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1797 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1798 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1799 if (operation.isCancelled) {
1800 [self requestFailed:containerRequest];
1801 } else if (containerRequest.responseStatusCode == 202) {
1802 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1803 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1804 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1805 if (missingBlockIndex == NSNotFound) {
1806 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1807 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1808 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1809 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1810 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1812 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1813 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1816 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1817 newObjectRequest.delegate = self;
1818 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1819 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1820 newObjectRequest.userInfo = containerRequest.userInfo;
1821 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1822 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1823 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1824 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1825 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1827 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1828 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1829 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1830 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1831 missingBlockIndex:missingBlockIndex
1832 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1833 newContainerRequest.delegate = self;
1834 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1835 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1836 newContainerRequest.userInfo = containerRequest.userInfo;
1837 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1838 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1839 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1840 [activityFacility updateActivity:activity
1841 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1842 totalBytes:activity.totalBytes
1843 currentBytes:(activity.currentBytes + size)];
1845 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1848 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1849 [self requestFailed:containerRequest];
1854 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1856 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1857 DLog(@"Move object finished: %@", objectRequest.url);
1858 if (operation.isCancelled) {
1859 [self requestFailed:objectRequest];
1860 } else if (objectRequest.responseStatusCode == 201) {
1861 dispatch_async(dispatch_get_main_queue(), ^{
1862 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1863 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1864 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1865 [node forceRefresh];
1867 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1870 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1871 [self forceRefresh:self];
1872 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1873 [self refresh:self];
1876 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1877 [self requestFailed:objectRequest];
1882 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1884 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1885 DLog(@"Copy object finished: %@", objectRequest.url);
1886 if (operation.isCancelled) {
1887 [self requestFailed:objectRequest];
1888 } else if (objectRequest.responseStatusCode == 201) {
1889 dispatch_async(dispatch_get_main_queue(), ^{
1890 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1891 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1892 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1893 [node forceRefresh];
1895 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1898 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1899 [self forceRefresh:self];
1900 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1901 [self refresh:self];
1904 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1905 [self requestFailed:objectRequest];
1910 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1912 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1913 DLog(@"Delete object finished: %@", objectRequest.url);
1914 if (operation.isCancelled) {
1915 [self requestFailed:objectRequest];
1916 } else if (objectRequest.responseStatusCode == 204) {
1917 dispatch_async(dispatch_get_main_queue(), ^{
1918 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1919 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1920 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1921 [node forceRefresh];
1923 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1926 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1927 [self forceRefresh:self];
1928 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1929 [self refresh:self];
1932 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1933 [self requestFailed:objectRequest];
1939 #pragma mark NSSplitViewDelegate
1941 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1942 if (splitView == verticalSplitView)
1945 return ([horizontalSplitView bounds].size.height - 108);
1948 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1949 if (splitView == verticalSplitView)
1952 return ([horizontalSplitView bounds].size.height - 108);
1955 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1956 if (splitView == verticalSplitView) {
1957 if (proposedPosition < 120)
1959 else if (proposedPosition > 220)
1962 return proposedPosition;
1964 return ([horizontalSplitView bounds].size.height - 108);
1969 #pragma mark NSOutlineViewDataSource
1971 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1972 if (!browserInitialized)
1976 if (item == containersNode)
1977 return containersNodeChildren.count;
1978 if (item == sharedNode)
1983 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1984 if (!browserInitialized)
1987 return (!index ? containersNode : sharedNode);
1988 if (item == sharedNode)
1989 return (!index ? mySharedNode : othersSharedNode);
1990 return [containersNodeChildren objectAtIndex:index];
1993 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1994 if ((item == containersNode) || (item == sharedNode))
1999 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2000 PithosNode *node = (PithosNode *)item;
2004 #pragma mark Drag and Drop destination
2006 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2007 validateDrop:(id<NSDraggingInfo>)info
2008 proposedItem:(id)item
2009 proposedChildIndex:(NSInteger)index {
2010 NSDragOperation result = NSDragOperationNone;
2011 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2013 PithosNode *dropNode = (PithosNode *)item;
2014 if ([dropNode class] != [PithosContainerNode class])
2016 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2017 result = NSDragOperationCopy;
2018 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2019 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2020 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2021 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2022 if (![dropNode isEqualTo:draggedParentNode])
2023 result = NSDragOperationMove;
2024 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2025 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2026 result = NSDragOperationCopy;
2032 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2033 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2034 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2035 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2036 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2037 PithosNode *node = (PithosNode *)item;
2038 DLog(@"drag in node: %@", node.url);
2039 return [self uploadFiles:filenames toNode:node];
2041 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2042 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2043 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2044 PithosNode *node = (PithosNode *)item;
2045 DLog(@"drag local node: %@", node.url);
2046 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2047 ([info draggingSourceOperationMask] & NSDragOperationMove))
2048 return [self moveNodes:draggedNodes toNode:node];
2049 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2050 return [self cpyNodes:draggedNodes toNode:node];
2057 #pragma mark NSOutlineViewDelegate
2059 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2060 if ((item == containersNode) || (item == sharedNode))
2065 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2066 if ((item == containersNode) || (item == sharedNode))
2071 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2072 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2075 [browser loadColumnZero];
2081 #pragma mark NSMenuDelegate
2083 - (void)menuNeedsUpdate:(NSMenu *)menu {
2084 [menu removeAllItems];
2085 NSMenuItem *menuItem;
2086 NSString *menuItemTitle;
2087 BOOL nodeContextMenu = NO;
2088 PithosNode *menuNode = nil;
2089 NSMutableArray *menuNodes;
2090 if (menu == browserMenu) {
2091 NSInteger column = [browser clickedColumn];
2092 NSInteger row = [browser clickedRow];
2093 if ((column == -1) || (row == -1)) {
2095 // General context menu
2096 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2097 if ([menuNodesIndexPaths count] == 0) {
2098 menuNode = [browser parentForItemsInColumn:0];
2099 } else if (([menuNodesIndexPaths count] != 1) ||
2100 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2101 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2103 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2106 menuNode = [browser parentForItemsInColumn:column];
2107 if ([menuNode class] == [PithosObjectNode class]) {
2108 // Node context menu
2109 menuNodes = [NSMutableArray arrayWithObject:menuNode];
2110 nodeContextMenu = YES;
2113 // General context menu
2116 // Node context menu
2117 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2118 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2119 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2120 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2121 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2122 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2125 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2127 nodeContextMenu = YES;
2129 } else if (menu == outlineViewMenu) {
2130 NSInteger row = [outlineView clickedRow];
2132 row = [outlineView selectedRow];
2135 menuNode = [outlineView itemAtRow:row];
2138 if (!nodeContextMenu) {
2139 // General context menu
2140 if (([menuNode class] == [PithosAccountNode class]) ||
2141 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2142 ([menuNode class] == [PithosEmptyNode class]))
2145 if (!menuNode.shared && !menuNode.sharingAccount) {
2146 menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""];
2147 [menuItem setRepresentedObject:menuNode];
2148 [menu addItem:menuItem];
2149 [menu addItem:[NSMenuItem separatorItem]];
2152 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2153 [menu addItem:menuItem];
2154 [menu addItem:[NSMenuItem separatorItem]];
2156 menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2157 action:@selector(menuGetInfo:)
2159 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2160 [menu addItem:menuItem];
2162 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2163 (([menuNode class] == [PithosContainerNode class]) ||
2164 (([menuNode class] == [PithosSubdirNode class]) &&
2165 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2166 NSUInteger clipboardNodesCount = [clipboardNodes count];
2167 if (clipboardNodesCount == 0) {
2168 self.clipboardNodes = nil;
2169 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2170 if (clipboardNodesCount == 1)
2171 menuItemTitle = @"Paste Item";
2173 menuItemTitle = @"Paste Items";
2174 [menu addItem:[NSMenuItem separatorItem]];
2175 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2176 [menuItem setRepresentedObject:menuNode];
2177 [menu addItem:menuItem];
2181 // Node context menu
2182 NSUInteger menuNodesCount = [menuNodes count];
2183 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2185 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2186 menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""];
2187 [menuItem setRepresentedObject:menuNodes];
2188 [menu addItem:menuItem];
2189 [menu addItem:[NSMenuItem separatorItem]];
2191 // Move to Trash (pithos container only)
2193 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2194 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2195 menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2196 action:@selector(menuMoveToTrash:)
2198 [menuItem setRepresentedObject:menuNodes];
2199 [menu addItem:menuItem];
2201 menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""];
2202 [menuItem setRepresentedObject:menuNodes];
2203 [menu addItem:menuItem];
2204 [menu addItem:[NSMenuItem separatorItem]];
2207 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2208 [menu addItem:menuItem];
2210 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2211 [menu addItem:[NSMenuItem separatorItem]];
2212 menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2213 action:@selector(menuGetInfo:)
2215 [menuItem setRepresentedObject:menuNodes];
2216 [menu addItem:menuItem];
2218 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2219 [menu addItem:[NSMenuItem separatorItem]];
2222 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2223 if (menuNodesCount == 1)
2224 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2226 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2227 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""];
2228 [menuItem setRepresentedObject:menuNodes];
2229 [menu addItem:menuItem];
2232 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2233 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2234 if (menuNodesCount == 1)
2235 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2237 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2238 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""];
2239 [menuItem setRepresentedObject:menuNodes];
2240 [menu addItem:menuItem];
2243 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2244 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2245 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2246 NSUInteger clipboardNodesCount = [clipboardNodes count];
2247 if (clipboardNodesCount == 0) {
2248 self.clipboardNodes = nil;
2249 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2250 if (clipboardNodesCount == 1)
2251 menuItemTitle = @"Paste Item";
2253 menuItemTitle = @"Paste Items";
2254 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2255 [menuItem setRepresentedObject:firstMenuNode];
2256 [menu addItem:menuItem];
2263 #pragma mark NSMenuValidation
2265 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2266 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2267 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2268 if ([menuNodesIndexPaths count] == 0)
2271 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2272 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2273 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2274 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2275 ((menuItem.action == @selector(delete:)) &&
2276 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2277 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2280 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2281 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2282 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2284 menuItem.representedObject = menuNodes;
2285 } else if (menuItem.action == @selector(paste:)) {
2286 if (!clipboardNodes || ![clipboardNodes count])
2289 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2290 PithosNode *menuNode;
2291 if ([menuNodesIndexPaths count] == 0)
2292 menuNode = [browser parentForItemsInColumn:0];
2293 else if (([menuNodesIndexPaths count] != 1) ||
2294 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2295 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2297 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2299 if (menuNode.shared || menuNode.sharingAccount ||
2300 (([menuNode class] != [PithosContainerNode class]) &&
2301 (([menuNode class] != [PithosSubdirNode class]) ||
2302 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2303 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2306 menuItem.representedObject = menuNode;
2311 - (void)cut:(NSMenuItem *)sender {
2312 [self menuCut:sender];
2315 - (void)copy:(NSMenuItem *)sender {
2316 [self menuCopy:sender];
2319 - (void)paste:(NSMenuItem *)sender {
2320 [self menuPaste:sender];
2323 - (void)delete:(NSMenuItem *)sender {
2324 if (sender.tag == 0)
2325 [self menuMoveToTrash:sender];
2327 [self menuDelete:sender];
2331 #pragma mark Menu Actions
2333 - (void)menuNewFolder:(NSMenuItem *)sender {
2334 PithosNode *node = (PithosNode *)[sender representedObject];
2335 if ([node class] == [PithosContainerNode class]) {
2336 // Operation: Create (upload) a new root application/directory object
2337 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2339 if (operation.isCancelled)
2341 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2342 containerName:node.pithosContainer.name
2343 subdirName:@"untitled folder"];
2344 NSString *fileName = [safeObjectName lastPathComponent];
2345 if (operation.isCancelled)
2347 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2348 containerName:node.pithosContainer.name
2349 objectName:safeObjectName
2351 contentType:@"application/directory"
2353 contentDisposition:nil
2356 isPublic:ASIPithosObjectRequestPublicIgnore
2358 data:[NSData data]];
2359 objectRequest.delegate = self;
2360 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2361 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2362 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2363 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2364 message:messagePrefix];
2365 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2366 fileName, @"fileName",
2367 [NSArray arrayWithObject:node], @"refreshNodes",
2368 [NSNumber numberWithBool:YES], @"refresh",
2369 activity, @"activity",
2370 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2371 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2372 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2373 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2374 [NSNumber numberWithUnsignedInteger:10], @"retries",
2375 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2376 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2377 uploadNetworkQueue, @"networkQueue",
2378 @"upload", @"operationType",
2380 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2383 [uploadQueue addOperation:operation];
2384 } else if (([node class] == [PithosSubdirNode class]) &&
2385 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2386 // Operation: Create (upload) a new aplication/directory object
2387 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2389 if (operation.isCancelled)
2391 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2392 containerName:node.pithosContainer.name
2393 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2394 NSString *fileName = [safeObjectName lastPathComponent];
2395 if (operation.isCancelled)
2397 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2398 containerName:node.pithosContainer.name
2399 objectName:safeObjectName
2401 contentType:@"application/directory"
2403 contentDisposition:nil
2406 isPublic:ASIPithosObjectRequestPublicIgnore
2408 data:[NSData data]];
2409 objectRequest.delegate = self;
2410 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2411 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2412 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2413 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2414 message:messagePrefix];
2415 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2416 fileName, @"fileName",
2417 [NSArray arrayWithObject:node], @"refreshNodes",
2418 [NSNumber numberWithBool:YES], @"refresh",
2419 activity, @"activity",
2420 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2421 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2422 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2423 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2424 [NSNumber numberWithUnsignedInteger:10], @"retries",
2425 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2426 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2427 uploadNetworkQueue, @"networkQueue",
2428 @"upload", @"operationType",
2430 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2433 [uploadQueue addOperation:operation];
2437 - (void)menuGetInfo:(NSMenuItem *)sender {
2438 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2439 [node showPithosNodeInfo:sender];
2443 - (void)menuDownload:(NSMenuItem *)sender {
2444 NSArray *nodes = (NSArray *)[sender representedObject];
2445 PithosNode *firstNode = [nodes objectAtIndex:0];
2446 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2447 NSSavePanel *save = [NSSavePanel savePanel];
2448 save.nameFieldStringValue = firstNode.displayName;
2449 NSInteger result = [save runModal];
2450 if (result == NSOKButton) {
2451 NSString *destinationPath = save.URL.path;
2452 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2453 NSString *newFileName = [destinationPath lastPathComponent];
2454 if ([destinationPath hasSuffix:@"/"])
2455 newFileName = [newFileName stringByAppendingString:@"/"];
2456 if ([firstNode.displayName isEqualToString:newFileName])
2458 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2461 NSOpenPanel *open = [NSOpenPanel openPanel];
2462 open.canChooseFiles = NO;
2463 open.canChooseDirectories = YES;
2464 open.canCreateDirectories = YES;
2465 NSInteger result = [open runModal];
2466 if (result == NSOKButton) {
2467 NSString *directoryPath = open.URL.path;
2468 for (PithosNode *node in nodes) {
2469 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2475 - (void)menuDelete:(NSMenuItem *)sender {
2476 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2477 if (([node class] == [PithosObjectNode class]) ||
2478 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2479 // Operation: Delete an object or subdir/ node
2480 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2482 if (operation.isCancelled)
2484 NSString *fileName = [node.pithosObject.name lastPathComponent];
2485 if ([node.pithosObject.name hasSuffix:@"/"])
2486 fileName = [fileName stringByAppendingString:@"/"];
2487 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2488 containerName:node.pithosContainer.name
2489 objectName:node.pithosObject.name];
2490 if (operation.isCancelled)
2492 objectRequest.delegate = self;
2493 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2494 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2495 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2496 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2497 message:messagePrefix];
2498 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2499 fileName, @"fileName",
2500 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2501 activity, @"activity",
2502 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2503 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2504 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2505 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2506 [NSNumber numberWithUnsignedInteger:10], @"retries",
2507 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2508 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2509 deleteNetworkQueue, @"networkQueue",
2510 @"delete", @"operationType",
2512 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2515 [deleteQueue addOperation:operation];
2516 } else if ([node class] == [PithosSubdirNode class]) {
2517 // Operation: Delete a subdir node and its descendants
2518 // The resulting ASIPithosObjectRequests are chained through dependencies
2519 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2521 if (operation.isCancelled)
2523 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2524 containerName:node.pithosContainer.name
2525 objectName:node.pithosObject.name];
2526 if (!operation.isCancelled && objectRequests) {
2527 ASIPithosObjectRequest *previousObjectRequest = nil;
2528 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2529 if (operation.isCancelled)
2531 objectRequest.delegate = self;
2532 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2533 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2534 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2535 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2536 message:messagePrefix];
2537 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2538 [NSDictionary dictionaryWithObjectsAndKeys:
2539 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2540 activity, @"activity",
2541 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2542 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2543 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2544 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2545 [NSNumber numberWithUnsignedInteger:10], @"retries",
2546 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2547 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2548 deleteNetworkQueue, @"networkQueue",
2549 @"delete", @"operationType",
2551 if (previousObjectRequest)
2552 [objectRequest addDependency:previousObjectRequest];
2553 previousObjectRequest = objectRequest;
2554 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2559 [deleteQueue addOperation:operation];
2564 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2565 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2566 if (([node class] == [PithosObjectNode class]) ||
2567 (([node class] == [PithosSubdirNode class]) &&
2568 !node.pithosObject.subdir &&
2569 [node.pithosObject.name hasSuffix:@"/"])) {
2570 // Operation: Move to trash an object or subdir/ node
2571 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2573 if (operation.isCancelled)
2575 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2576 containerName:@"trash"
2577 objectName:node.pithosObject.name];
2578 if (!operation.isCancelled && safeObjectName) {
2579 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2580 containerName:node.pithosContainer.name
2581 objectName:node.pithosObject.name
2582 destinationContainerName:@"trash"
2583 destinationObjectName:safeObjectName
2585 if (!operation.isCancelled && objectRequest) {
2586 objectRequest.delegate = self;
2587 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2588 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2589 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2590 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2591 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2592 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2593 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2594 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2595 message:messagePrefix];
2596 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2597 [NSDictionary dictionaryWithObjectsAndKeys:
2598 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2599 activity, @"activity",
2600 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2601 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2602 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2603 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2604 [NSNumber numberWithUnsignedInteger:10], @"retries",
2605 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2606 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2607 moveNetworkQueue, @"networkQueue",
2608 @"move", @"operationType",
2610 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2615 [moveQueue addOperation:operation];
2616 } else if ([node class] == [PithosSubdirNode class]) {
2617 // Operation: Move to trash a subdir node and its descendants
2618 // The resulting ASIPithosObjectRequests are chained through dependencies
2619 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2621 if (operation.isCancelled)
2623 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2624 containerName:@"trash"
2625 subdirName:node.pithosObject.name];
2626 if (!operation.isCancelled && safeObjectName) {
2627 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2628 containerName:node.pithosContainer.name
2629 objectName:node.pithosObject.name
2630 destinationContainerName:@"trash"
2631 destinationObjectName:safeObjectName
2633 if (!operation.isCancelled && objectRequests) {
2634 ASIPithosObjectRequest *previousObjectRequest = nil;
2635 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2636 if (operation.isCancelled)
2638 objectRequest.delegate = self;
2639 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2640 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2641 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2642 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2643 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2644 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2645 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2646 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2647 message:messagePrefix];
2648 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2649 [NSDictionary dictionaryWithObjectsAndKeys:
2650 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2651 activity, @"activity",
2652 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2653 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2654 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2655 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2656 [NSNumber numberWithUnsignedInteger:10], @"retries",
2657 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2658 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2659 moveNetworkQueue, @"networkQueue",
2660 @"move", @"operationType",
2662 if (previousObjectRequest)
2663 [objectRequest addDependency:previousObjectRequest];
2664 previousObjectRequest = objectRequest;
2665 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2671 [moveQueue addOperation:operation];
2676 - (void)menuCut:(NSMenuItem *)sender {
2677 self.clipboardNodes = (NSArray *)[sender representedObject];
2678 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2679 self.clipboardCopy = NO;
2682 - (void)menuCopy:(NSMenuItem *)sender {
2683 self.clipboardNodes = (NSArray *)[sender representedObject];
2684 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2685 self.clipboardCopy = YES;
2688 - (void)menuPaste:(NSMenuItem *)sender {
2689 if (!clipboardNodes || ![clipboardNodes count])
2691 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2692 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2693 if (clipboardCopy) {
2694 [self cpyNodes:localClipboardNodes toNode:dropNode];
2695 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2696 self.clipboardNodes = nil;
2697 self.clipboardParentNode = nil;
2698 [self moveNodes:localClipboardNodes toNode:dropNode];
2703 #pragma mark PithosActivityFacilityDelegate
2705 - (void)activityUpdate:(NSDictionary *)info {
2706 NSString *message = [info objectForKey:@"message"];
2707 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2708 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2709 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2710 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2711 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2712 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2713 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2714 if (runningActivitiesCount && totalBytes) {
2715 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2716 [activityProgressIndicator startAnimation:self];
2718 [activityProgressIndicator setDoubleValue:1.0];
2719 [activityProgressIndicator stopAnimation:self];
2723 message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
2724 [activityTextField setStringValue:message];