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 pithosAccountManager, 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 accountNode.pithosAccountManager = pithosAccountManager;
216 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
217 containersNodeChildren = [[NSMutableArray alloc] init];
218 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
219 mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
220 mySharedNode.pithosAccountManager = pithosAccountManager;
221 mySharedNode.displayName = @"shared by me";
222 mySharedNode.shared = YES;
223 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
224 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
225 othersSharedNode.pithosAccountManager = pithosAccountManager;
226 othersSharedNode.displayName = @"shared with me";
227 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
229 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[PithosOutlineViewCell alloc] init]];
231 // Register for updates
232 // PithosAccountNode accountNode updates outlineView container nodes
233 [[NSNotificationCenter defaultCenter] addObserver:self
234 selector:@selector(pithosAccountNodeChildrenUpdated:)
235 name:@"PithosNodeChildrenUpdated"
237 // PithosNode updates browser nodes
238 [[NSNotificationCenter defaultCenter] addObserver:self
239 selector:@selector(pithosNodeChildrenUpdated:)
240 name:@"PithosNodeChildrenUpdated"
242 // Request for browser refresh
243 [[NSNotificationCenter defaultCenter] addObserver:self
244 selector:@selector(pithosBrowserRefreshNeeded:)
245 name:@"PithosBrowserRefreshNeeeded"
249 - (void)resetBrowser {
250 @synchronized(self) {
255 [refreshTimer invalidate];
257 [moveNetworkQueue reset];
258 [copyNetworkQueue reset];
259 [deleteNetworkQueue reset];
260 [uploadNetworkQueue reset];
261 [downloadNetworkQueue reset];
263 [moveQueue cancelAllOperations];
264 [moveQueue setSuspended:YES];
265 [copyQueue cancelAllOperations];
266 [copyQueue setSuspended:YES];
267 [deleteQueue cancelAllOperations];
268 [deleteQueue setSuspended:YES];
269 [uploadQueue cancelAllOperations];
270 [uploadQueue setSuspended:YES];
271 [downloadQueue cancelAllOperations];
272 [downloadQueue setSuspended:YES];
274 [moveCallbackQueue cancelAllOperations];
275 [moveCallbackQueue setSuspended:YES];
276 [copyCallbackQueue cancelAllOperations];
277 [copyCallbackQueue setSuspended:YES];
278 [deleteCallbackQueue cancelAllOperations];
279 [deleteCallbackQueue setSuspended:YES];
280 [uploadCallbackQueue cancelAllOperations];
281 [uploadCallbackQueue setSuspended:YES];
282 [downloadCallbackQueue cancelAllOperations];
283 [downloadCallbackQueue setSuspended:YES];
286 [browser loadColumnZero];
287 [containersNodeChildren removeAllObjects];
288 [outlineView reloadData];
289 // Expand the folder outline view
290 [outlineView expandItem:nil expandChildren:YES];
291 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
293 activityFacility.delegate = nil;
294 [activityProgressIndicator setDoubleValue:1.0];
295 [activityProgressIndicator stopAnimation:self];
297 @synchronized(self) {
302 - (void)startBrowser {
303 @synchronized(self) {
308 // In the improbable case of leftover operations
309 [moveNetworkQueue reset];
310 [copyNetworkQueue reset];
311 [deleteNetworkQueue reset];
312 [uploadNetworkQueue reset];
313 [downloadNetworkQueue reset];
314 [moveQueue cancelAllOperations];
315 [copyQueue cancelAllOperations];
316 [deleteQueue cancelAllOperations];
317 [uploadQueue cancelAllOperations];
318 [downloadQueue cancelAllOperations];
319 [moveCallbackQueue cancelAllOperations];
320 [copyCallbackQueue cancelAllOperations];
321 [deleteCallbackQueue cancelAllOperations];
322 [uploadCallbackQueue cancelAllOperations];
323 [downloadCallbackQueue cancelAllOperations];
325 [moveNetworkQueue go];
326 [copyNetworkQueue go];
327 [deleteNetworkQueue go];
328 [uploadNetworkQueue go];
329 [downloadNetworkQueue go];
330 [moveQueue setSuspended:NO];
331 [copyQueue setSuspended:NO];
332 [deleteQueue setSuspended:NO];
333 [uploadQueue setSuspended:NO];
334 [downloadQueue setSuspended:NO];
335 [moveCallbackQueue setSuspended:NO];
336 [copyCallbackQueue setSuspended:NO];
337 [deleteCallbackQueue setSuspended:NO];
338 [uploadCallbackQueue setSuspended:NO];
339 [downloadCallbackQueue setSuspended:NO];
341 accountNode.pithos = pithos;
342 accountNode.pithosAccountManager = pithosAccountManager;
343 [accountNode forceRefresh];
344 mySharedNode.pithos = pithos;
345 mySharedNode.pithosAccountManager = pithosAccountManager;
346 [mySharedNode forceRefresh];
347 othersSharedNode.pithos = pithos;
348 othersSharedNode.pithosAccountManager = pithosAccountManager;
349 [othersSharedNode forceRefresh];
351 // [activityFacility reset];
352 activityFacility.delegate = self;
354 refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
356 selector:@selector(forceRefresh:)
359 @synchronized(self) {
364 - (BOOL)operationsPending {
365 return ([moveNetworkQueue operationCount] ||
366 [copyNetworkQueue operationCount] ||
367 [deleteNetworkQueue operationCount] ||
368 [uploadNetworkQueue operationCount] ||
369 [downloadNetworkQueue operationCount] ||
370 [moveQueue operationCount] ||
371 [copyQueue operationCount] ||
372 [deleteQueue operationCount] ||
373 [uploadQueue operationCount] ||
374 [downloadQueue operationCount] ||
375 [moveCallbackQueue operationCount] ||
376 [copyCallbackQueue operationCount] ||
377 [deleteCallbackQueue operationCount] ||
378 [uploadCallbackQueue operationCount] ||
379 [downloadCallbackQueue operationCount]);
383 [[NSNotificationCenter defaultCenter] removeObserver:self];
387 - (void)setPithos:(ASIPithos *)aPithos {
389 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
390 ![aPithos.authToken isEqualToString:pithos.authToken] ||
391 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
392 ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
404 #pragma mark Observers
406 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
407 PithosNode *node = (PithosNode *)[notification object];
408 if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
410 DLog(@"pithosNodeChildrenUpdated:%@", node.url);
411 NSInteger lastColumn = [browser lastColumn];
412 for (NSInteger column = lastColumn; column >= 0; column--) {
413 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
414 [browser reloadColumn:column];
420 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
421 BOOL containerPithosFound = NO;
422 BOOL containerTrashFound = NO;
423 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
424 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
425 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
426 [removedContainersNodeChildren addIndex:i];
428 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
429 for (PithosContainerNode *containerNode in accountNode.children) {
430 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
431 if (![containersNodeChildren containsObject:containerNode])
432 [containersNodeChildren insertObject:containerNode atIndex:0];
433 containerPithosFound = YES;
434 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
435 NSUInteger insertIndex = 1;
436 if (!containerPithosFound)
438 if (![containersNodeChildren containsObject:containerNode])
439 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
440 containerTrashFound = YES;
441 } else if (![containersNodeChildren containsObject:containerNode]) {
442 [containersNodeChildren addObject:containerNode];
445 BOOL refreshAccountNode = NO;
446 if (!containerPithosFound) {
447 // Create pithos node
448 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
449 containerName:@"pithos"];
450 [PithosUtilities startAndWaitForRequest:containerRequest];
451 if ([containerRequest error]) {
452 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
454 refreshAccountNode = YES;
457 if (!containerTrashFound) {
459 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
460 containerName:@"trash"];
461 [PithosUtilities startAndWaitForRequest:containerRequest];
462 if ([containerRequest error]) {
463 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
465 refreshAccountNode = YES;
469 if (refreshAccountNode)
470 [accountNode refresh];
472 [outlineView reloadData];
474 // Expand the folder outline view
475 [outlineView expandItem:nil expandChildren:YES];
477 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
478 rootNode = [containersNodeChildren objectAtIndex:0];
479 [browser loadColumnZero];
486 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
493 - (IBAction)forceRefresh:(id)sender {
497 [accountNode forceRefresh];
498 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
499 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
500 node.forcedRefresh = YES;
501 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
503 [browser validateVisibleColumns];
506 - (IBAction)refresh:(id)sender {
509 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
510 [self forceRefresh:sender];
513 [accountNode refresh];
514 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
515 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
517 [browser validateVisibleColumns];
522 #pragma mark NSBrowserDelegate
524 - (id)rootItemForBrowser:(NSBrowser *)browser {
528 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
529 PithosNode *node = (PithosNode *)item;
530 return node.children.count;
533 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
534 PithosNode *node = (PithosNode *)item;
535 return [node.children objectAtIndex:index];
538 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
539 PithosNode *node = (PithosNode *)item;
540 return node.isLeafItem;
543 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
544 PithosNode *node = (PithosNode *)item;
548 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
549 if (sharedPreviewController == nil)
550 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
551 return sharedPreviewController;
554 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
555 // if (!forUserResize) {
556 // id item = [browser parentForItemsInColumn:columnIndex];
557 // if ([self browser:browser isLeafItem:item]) {
558 // suggestedWidth = 200;
561 // return suggestedWidth;
564 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
570 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
571 PithosNode *node = (PithosNode *)item;
572 if (node.shared || node.sharingAccount ||
573 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
579 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
581 PithosNode *node = (PithosNode *)item;
582 NSString *newName = (NSString *)object;
583 NSUInteger newNameLength = [newName length];
584 NSRange firstSlashRange = [newName rangeOfString:@"/"];
585 if ((newNameLength == 0) ||
586 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
587 ([newName isEqualToString:node.displayName])) {
590 if (([node class] == [PithosObjectNode class]) ||
591 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
592 // Operation: Rename (move) an object or subdir/ node
593 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
595 if (operation.isCancelled)
597 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
598 if ([newName hasSuffix:@"/"])
599 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
600 NSError *error = nil;
602 if ([PithosUtilities objectExistsAtPithos:pithos
603 containerName:node.pithosContainer.name
604 objectName:destinationObjectName
606 isDirectory:&isDirectory
607 sharingAccount:nil]) {
608 dispatch_async(dispatch_get_main_queue(), ^{
609 NSAlert *alert = [[NSAlert alloc] init];
610 [alert setMessageText:@"Name Taken"];
611 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
612 [alert addButtonWithTitle:@"OK"];
619 if (operation.isCancelled)
621 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
622 containerName:node.pithosContainer.name
623 objectName:node.pithosObject.name
624 destinationContainerName:node.pithosContainer.name
625 destinationObjectName:destinationObjectName
627 if (!operation.isCancelled && objectRequest) {
628 objectRequest.delegate = self;
629 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
630 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
631 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
632 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
633 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
634 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
635 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
636 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
637 message:messagePrefix];
638 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
639 [NSDictionary dictionaryWithObjectsAndKeys:
640 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
641 [NSNumber numberWithBool:YES], @"refresh",
642 activity, @"activity",
643 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
644 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
645 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
646 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
647 [NSNumber numberWithUnsignedInteger:10], @"retries",
648 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
649 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
650 moveNetworkQueue, @"networkQueue",
651 @"move", @"operationType",
653 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
657 [moveQueue addOperation:operation];
658 } else if ([node class] == [PithosSubdirNode class]) {
659 if (firstSlashRange.length == 1)
661 // Operation: Rename (move) a subdir node and its descendants
662 // The resulting ASIPithosObjectRequests are chained through dependencies
663 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
665 if (operation.isCancelled)
667 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
668 NSError *error = nil;
670 if ([PithosUtilities objectExistsAtPithos:pithos
671 containerName:node.pithosContainer.name
672 objectName:destinationObjectName
674 isDirectory:&isDirectory
675 sharingAccount:nil]) {
676 dispatch_async(dispatch_get_main_queue(), ^{
677 NSAlert *alert = [[NSAlert alloc] init];
678 [alert setMessageText:@"Name Taken"];
679 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
680 [alert addButtonWithTitle:@"OK"];
687 if (operation.isCancelled)
689 if (node.pithosObject.subdir)
690 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
691 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
692 containerName:node.pithosContainer.name
693 objectName:node.pithosObject.name
694 destinationContainerName:node.pithosContainer.name
695 destinationObjectName:destinationObjectName
697 if (!operation.isCancelled && objectRequests) {
698 ASIPithosObjectRequest *previousObjectRequest = nil;
699 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
700 if (operation.isCancelled)
702 objectRequest.delegate = self;
703 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
704 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
705 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
706 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
707 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
708 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
709 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
710 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
711 message:messagePrefix];
712 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
713 [NSDictionary dictionaryWithObjectsAndKeys:
714 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
715 [NSNumber numberWithBool:YES], @"refresh",
716 activity, @"activity",
717 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
718 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
719 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
720 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
721 [NSNumber numberWithUnsignedInteger:10], @"retries",
722 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
723 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
724 moveNetworkQueue, @"networkQueue",
725 @"move", @"operationType",
727 if (previousObjectRequest)
728 [objectRequest addDependency:previousObjectRequest];
729 previousObjectRequest = objectRequest;
730 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
735 [moveQueue addOperation:operation];
739 #pragma mark Drag and Drop source
741 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
742 withEvent:(NSEvent *)event {
743 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
744 __block BOOL result = YES;
745 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
746 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
747 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
755 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
756 toPasteboard:(NSPasteboard *)pasteboard {
757 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
758 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
759 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
760 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
761 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
762 [propertyList addObject:[node.pithosObject.name pathExtension]];
763 [nodes addObject:node];
766 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
767 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
768 self.draggedNodes = nodes;
769 self.draggedParentNode = [browser parentForItemsInColumn:column];
773 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
774 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
775 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
776 for (PithosNode *node in draggedNodes) {
777 [names addObject:node.displayName];
778 // If the node is a subdir ask if the whole tree should be downloaded
779 if ([node class] == [PithosSubdirNode class]) {
780 NSAlert *alert = [[NSAlert alloc] init];
781 [alert setMessageText:@"Download directory"];
782 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
783 [alert addButtonWithTitle:@"OK"];
784 [alert addButtonWithTitle:@"Cancel"];
785 NSInteger choice = [alert runModal];
786 if (choice == NSAlertFirstButtonReturn)
787 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
789 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
795 #pragma mark Drag and Drop destination
797 - (NSDragOperation)browser:aBrowser
798 validateDrop:(id<NSDraggingInfo>)info
799 proposedRow:(NSInteger *)row
800 column:(NSInteger *)column
801 dropOperation:(NSBrowserDropOperation *)dropOperation {
802 NSDragOperation result = NSDragOperationNone;
803 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
804 // For a drop above, the drop is redirected to the parent item
805 if (*dropOperation == NSBrowserDropAbove)
807 // Only allow dropping in folders
809 PithosNode *dropNode;
811 // Check if the node is not a folder and if so redirect to the parent item
812 dropNode = [browser itemAtRow:*row inColumn:*column];
813 if ([dropNode class] == [PithosObjectNode class])
817 dropNode = [browser parentForItemsInColumn:*column];
819 if (!dropNode.shared &&
820 (!dropNode.sharingAccount ||
821 ([dropNode class] == [PithosSubdirNode class]) ||
822 ([dropNode class] == [PithosContainerNode class]))) {
823 *dropOperation = NSBrowserDropOn;
824 result = NSDragOperationCopy;
827 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
828 // For a drop above, the drop is redirected to the parent item
829 if (*dropOperation == NSBrowserDropAbove)
831 // Only allow dropping in folders
833 PithosNode *dropNode;
835 // Check if the node is not a folder and if so redirect to the parent item
836 dropNode = [browser itemAtRow:*row inColumn:*column];
837 if ([dropNode class] == [PithosObjectNode class])
841 dropNode = [browser parentForItemsInColumn:*column];
843 if (!dropNode.shared && !dropNode.sharingAccount) {
844 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
845 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
846 if ((([dropNode class] == [PithosContainerNode class]) ||
847 dropNode.pithosObject.subdir ||
848 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
849 ![dropNode isEqualTo:draggedParentNode]) {
850 // ![dropNode isEqualTo:draggedParentNode] &&
851 // ![draggedNodes containsObject:dropNode]) {
852 result = NSDragOperationMove;
854 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
855 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
856 if (([dropNode class] == [PithosContainerNode class]) ||
857 dropNode.pithosObject.subdir ||
858 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
859 result = NSDragOperationCopy;
868 - (BOOL)browser:(NSBrowser *)aBrowser
869 acceptDrop:(id<NSDraggingInfo>)info
871 column:(NSInteger)column
872 dropOperation:(NSBrowserDropOperation)dropOperation {
873 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
874 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
875 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
876 if ((column != -1) && (filenames != nil)) {
879 node = [browser itemAtRow:row inColumn:column];
881 node = [browser parentForItemsInColumn:column];
882 DLog(@"drag in node: %@", node.url);
883 return [self uploadFiles:filenames toNode:node];
885 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
886 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
887 if ((column != -1) && (draggedNodes != nil)) {
890 node = [browser itemAtRow:row inColumn:column];
892 node = [browser parentForItemsInColumn:column];
893 DLog(@"drag local node: %@", node.url);
894 if ([info draggingSourceOperationMask] & NSDragOperationMove)
895 return [self moveNodes:draggedNodes toNode:node];
896 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
897 return [self cpyNodes:draggedNodes toNode:node];
904 #pragma mark NSBrowser Actions
906 - (void)browserDoubleAction:(id)sender {
907 NSInteger column = [browser clickedColumn];
908 NSInteger row = [browser clickedRow];
909 if ((column == -1) || (row == -1))
911 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
912 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
913 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
914 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
915 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
916 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
919 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
921 NSMenuItem *menuItem = [[NSMenuItem alloc] init];
922 menuItem.representedObject = menuNodes;
923 [self menuDownload:menuItem];
927 #pragma mark Drag and Drop methods
929 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
930 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
931 if ([node class] == [PithosSubdirNode class]) {
932 // XXX newFilename and version are ignored in the case of a subdir node for now
933 // Operation: Download a subdir node and its descendants
934 // The resulting ASIPithosObjectRequests are chained through dependencies
935 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
937 if (operation.isCancelled)
939 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
940 containerName:node.pithosContainer.name
941 objectName:node.pithosObject.name
943 checkIfExists:checkIfExists
944 sharingAccount:node.sharingAccount];
945 if (!operation.isCancelled && objectRequests) {
946 ASIPithosObjectRequest *previousObjectRequest = nil;
947 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
948 if (operation.isCancelled)
950 objectRequest.delegate = self;
951 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
952 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
953 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
954 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
955 message:[messagePrefix stringByAppendingString:@" (0%)"]
956 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
958 dispatch_async(dispatch_get_main_queue(), ^{
959 [activityFacility updateActivity:activity withMessage:activity.message];
961 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
962 [NSDictionary dictionaryWithObjectsAndKeys:
963 activity, @"activity",
964 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
965 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
966 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
967 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
968 [NSNumber numberWithUnsignedInteger:10], @"retries",
969 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
970 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
971 downloadNetworkQueue, @"networkQueue",
972 @"download", @"operationType",
974 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
975 [activityFacility updateActivity:activity
976 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
977 totalBytes:activity.totalBytes
978 currentBytes:(activity.currentBytes + size)];
980 if (previousObjectRequest)
981 [objectRequest addDependency:previousObjectRequest];
982 previousObjectRequest = objectRequest;
983 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
988 [downloadQueue addOperation:operation];
989 } else if ([node class] == [PithosObjectNode class]) {
990 // Operation: Download an object node
991 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
993 if (operation.isCancelled)
995 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
996 containerName:node.pithosContainer.name
997 objectName:node.pithosObject.name
1000 withNewFileName:newFileName
1001 checkIfExists:checkIfExists
1002 sharingAccount:node.sharingAccount];
1003 if (!operation.isCancelled && objectRequest) {
1004 objectRequest.delegate = self;
1005 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1006 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1007 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1008 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1009 message:[messagePrefix stringByAppendingString:@" (0%)"]
1010 totalBytes:node.pithosObject.bytes
1012 dispatch_async(dispatch_get_main_queue(), ^{
1013 [activityFacility updateActivity:activity withMessage:activity.message];
1015 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1016 [NSDictionary dictionaryWithObjectsAndKeys:
1017 activity, @"activity",
1018 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1019 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1020 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1021 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1022 [NSNumber numberWithUnsignedInteger:10], @"retries",
1023 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1024 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1025 downloadNetworkQueue, @"networkQueue",
1026 @"download", @"operationType",
1028 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1029 [activityFacility updateActivity:activity
1030 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1031 totalBytes:activity.totalBytes
1032 currentBytes:(activity.currentBytes + size)];
1034 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1038 [downloadQueue addOperation:operation];
1042 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1043 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1045 NSFileManager *fileManager = [NSFileManager defaultManager];
1046 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1047 NSString *objectNamePrefix;
1048 if ([destinationNode class] == [PithosSubdirNode class])
1049 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1051 objectNamePrefix = [NSString string];
1052 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1053 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1054 containerName:containerName];
1055 [PithosUtilities startAndWaitForRequest:containerRequest];
1056 if ([containerRequest error]) {
1057 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1059 } else if (containerRequest.responseStatusCode != 204) {
1060 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1063 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1064 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1066 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1067 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1069 for (NSString *filePath in filenames) {
1071 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1074 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1075 precomposedStringWithCanonicalMapping];
1076 // Operation: Upload a local file
1077 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1079 if (operation.isCancelled)
1081 NSError *error = nil;
1082 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1083 if (contentType == nil)
1084 contentType = @"application/octet-stream";
1087 DLog(@"contentType detection error: %@", error);
1089 NSArray *hashes = nil;
1090 if (operation.isCancelled)
1092 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1093 containerName:containerName
1094 objectName:objectName
1095 contentType:contentType
1101 sharingAccount:destinationNode.sharingAccount];
1102 if (!operation.isCancelled && objectRequest) {
1103 objectRequest.delegate = self;
1104 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1105 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1106 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1107 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1108 message:[messagePrefix stringByAppendingString:@" (0%)"]
1109 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1111 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1112 [NSDictionary dictionaryWithObjectsAndKeys:
1113 containerName, @"containerName",
1114 objectName, @"objectName",
1115 contentType, @"contentType",
1116 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1117 blockHash, @"blockHash",
1118 filePath, @"filePath",
1120 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1121 [NSNumber numberWithBool:YES], @"refresh",
1122 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1123 activity, @"activity",
1124 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1125 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1126 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1127 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1128 [NSNumber numberWithUnsignedInteger:10], @"retries",
1129 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1130 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1131 uploadNetworkQueue, @"networkQueue",
1132 @"upload", @"operationType",
1134 if (destinationNode.sharingAccount)
1135 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1136 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1140 [uploadQueue addOperation:operation];
1142 // Upload directory, confirm first
1143 NSAlert *alert = [[NSAlert alloc] init];
1144 [alert setMessageText:@"Upload directory"];
1145 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1146 [alert addButtonWithTitle:@"OK"];
1147 [alert addButtonWithTitle:@"Cancel"];
1148 NSInteger choice = [alert runModal];
1149 if (choice == NSAlertFirstButtonReturn) {
1150 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1151 precomposedStringWithCanonicalMapping];
1152 // Operation: Upload a local directory and its descendants
1153 // The resulting ASIPithosObjectRequests are chained through dependencies
1154 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1156 if (operation.isCancelled)
1158 NSMutableArray *objectNames = nil;
1159 NSMutableArray *contentTypes = nil;
1160 NSMutableArray *filePaths = nil;
1161 NSMutableArray *hashesArrays = nil;
1162 NSMutableArray *directoryObjectRequests = nil;
1163 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1164 containerName:containerName
1165 objectName:objectName
1168 forDirectory:filePath
1170 objectNames:&objectNames
1171 contentTypes:&contentTypes
1172 filePaths:&filePaths
1173 hashesArrays:&hashesArrays
1174 directoryObjectRequests:&directoryObjectRequests
1175 sharingAccount:destinationNode.sharingAccount];
1176 if (operation.isCancelled)
1178 ASIPithosObjectRequest *previousObjectRequest = nil;
1179 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1180 if (operation.isCancelled)
1182 objectRequest.delegate = self;
1183 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1184 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1185 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1186 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1187 message:messagePrefix];
1188 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1189 [NSDictionary dictionaryWithObjectsAndKeys:
1190 [NSNumber numberWithBool:YES], @"refresh",
1191 activity, @"activity",
1192 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1193 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1194 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1195 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1196 [NSNumber numberWithUnsignedInteger:10], @"retries",
1197 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1198 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1199 uploadNetworkQueue, @"networkQueue",
1200 @"upload", @"operationType",
1202 if (previousObjectRequest)
1203 [objectRequest addDependency:previousObjectRequest];
1204 previousObjectRequest = objectRequest;
1205 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1207 if (!operation.isCancelled && objectRequests) {
1208 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1209 if (operation.isCancelled)
1211 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1212 objectRequest.delegate = self;
1213 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1214 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1215 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1216 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1217 message:[messagePrefix stringByAppendingString:@" (0%)"]
1218 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1220 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1221 [NSDictionary dictionaryWithObjectsAndKeys:
1222 containerName, @"containerName",
1223 [objectNames objectAtIndex:i], @"objectName",
1224 [contentTypes objectAtIndex:i], @"contentType",
1225 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1226 blockHash, @"blockHash",
1227 [filePaths objectAtIndex:i], @"filePath",
1228 [hashesArrays objectAtIndex:i], @"hashes",
1229 [NSNumber numberWithBool:YES], @"refresh",
1230 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1231 activity, @"activity",
1232 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1233 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1234 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1235 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1236 [NSNumber numberWithUnsignedInteger:10], @"retries",
1237 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1238 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1239 uploadNetworkQueue, @"networkQueue",
1240 @"upload", @"operationType",
1242 if (destinationNode.sharingAccount)
1243 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1244 if (previousObjectRequest)
1245 [objectRequest addDependency:previousObjectRequest];
1246 previousObjectRequest = objectRequest;
1247 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1252 [uploadQueue addOperation:operation];
1260 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1261 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1262 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1264 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1265 NSString *objectNamePrefix;
1266 if ([destinationNode class] == [PithosSubdirNode class])
1267 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1269 objectNamePrefix = [NSString string];
1271 for (PithosNode *node in nodes) {
1272 if (([node class] == [PithosObjectNode class]) ||
1273 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1274 // Operation: Move an object or subdir/ node
1275 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1277 if (operation.isCancelled)
1279 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1280 if ([node.pithosObject.name hasSuffix:@"/"])
1281 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1282 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1283 containerName:node.pithosContainer.name
1284 objectName:node.pithosObject.name
1285 destinationContainerName:containerName
1286 destinationObjectName:destinationObjectName
1288 if (!operation.isCancelled && objectRequest) {
1289 objectRequest.delegate = self;
1290 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1291 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1292 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1293 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1294 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1295 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1296 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1297 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1298 message:messagePrefix];
1299 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1300 [NSDictionary dictionaryWithObjectsAndKeys:
1301 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1302 activity, @"activity",
1303 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1304 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1305 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1306 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1307 [NSNumber numberWithUnsignedInteger:10], @"retries",
1308 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1309 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1310 moveNetworkQueue, @"networkQueue",
1311 @"move", @"operationType",
1313 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1317 [moveQueue addOperation:operation];
1318 } else if ([node class] == [PithosSubdirNode class]) {
1319 // Operation: Move a subdir node and its descendants
1320 // The resulting ASIPithosObjectRequests are chained through dependencies
1321 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1323 if (operation.isCancelled)
1325 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1326 if (node.pithosObject.subdir)
1327 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1328 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1329 containerName:node.pithosContainer.name
1330 objectName:node.pithosObject.name
1331 destinationContainerName:containerName
1332 destinationObjectName:destinationObjectName
1334 if (!operation.isCancelled && objectRequests) {
1335 ASIPithosObjectRequest *previousObjectRequest = nil;
1336 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1337 if (operation.isCancelled)
1339 objectRequest.delegate = self;
1340 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1341 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1342 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1343 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1344 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1345 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1346 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1347 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1348 message:messagePrefix];
1349 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1350 [NSDictionary dictionaryWithObjectsAndKeys:
1351 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1352 [NSNumber numberWithBool:YES], @"refresh",
1353 activity, @"activity",
1354 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1355 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1356 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1357 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1358 [NSNumber numberWithUnsignedInteger:10], @"retries",
1359 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1360 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1361 moveNetworkQueue, @"networkQueue",
1362 @"move", @"operationType",
1364 if (previousObjectRequest)
1365 [objectRequest addDependency:previousObjectRequest];
1366 previousObjectRequest = objectRequest;
1367 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1372 [moveQueue addOperation:operation];
1378 - (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1379 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1380 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1382 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1383 NSString *objectNamePrefix;
1384 if ([destinationNode class] == [PithosSubdirNode class])
1385 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1387 objectNamePrefix = [NSString string];
1389 for (PithosNode *node in nodes) {
1390 if (([node class] == [PithosObjectNode class]) ||
1391 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1392 // Operation: Copy an object or subdir/ node
1393 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1395 if (operation.isCancelled)
1397 NSString *destinationObjectName;
1398 if (![destinationNode isEqualTo:node.parent]) {
1399 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1400 if ([node.pithosObject.name hasSuffix:@"/"])
1401 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1403 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1404 containerName:containerName
1405 objectName:node.pithosObject.name];
1407 if (operation.isCancelled)
1409 ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithos
1410 containerName:node.pithosContainer.name
1411 objectName:node.pithosObject.name
1412 destinationContainerName:containerName
1413 destinationObjectName:destinationObjectName
1415 sharingAccount:node.sharingAccount];
1416 if (!operation.isCancelled && objectRequest) {
1417 objectRequest.delegate = self;
1418 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1419 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1420 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1421 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1422 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1423 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1424 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1425 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1426 message:messagePrefix];
1427 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1428 [NSDictionary dictionaryWithObjectsAndKeys:
1429 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1430 activity, @"activity",
1431 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1432 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1433 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1434 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1435 [NSNumber numberWithUnsignedInteger:10], @"retries",
1436 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1437 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1438 copyNetworkQueue, @"networkQueue",
1439 @"copy", @"operationType",
1441 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1445 [copyQueue addOperation:operation];
1446 } else if ([node class] == [PithosSubdirNode class]) {
1447 // Operation: Copy a subdir node and its descendants
1448 // The resulting ASIPithosObjectRequests are chained through dependencies
1449 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1451 if (operation.isCancelled)
1453 NSString *destinationObjectName;
1454 if (![destinationNode isEqualTo:node.parent]) {
1455 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1456 if (node.pithosObject.subdir)
1457 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1459 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1460 containerName:containerName
1461 subdirName:node.pithosObject.name];
1463 if (operation.isCancelled)
1465 NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithos
1466 containerName:node.pithosContainer.name
1467 objectName:node.pithosObject.name
1468 destinationContainerName:containerName
1469 destinationObjectName:destinationObjectName
1471 sharingAccount:node.sharingAccount];
1472 if (!operation.isCancelled && objectRequests) {
1473 ASIPithosObjectRequest *previousObjectRequest = nil;
1474 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1475 if (operation.isCancelled)
1477 objectRequest.delegate = self;
1478 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1479 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1480 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1481 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1482 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1483 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1484 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1485 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1486 message:messagePrefix];
1487 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1488 [NSDictionary dictionaryWithObjectsAndKeys:
1489 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1490 activity, @"activity",
1491 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1492 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1493 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1494 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1495 [NSNumber numberWithUnsignedInteger:10], @"retries",
1496 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1497 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1498 copyNetworkQueue, @"networkQueue",
1499 @"copy", @"operationType",
1501 if (previousObjectRequest)
1502 [objectRequest addDependency:previousObjectRequest];
1503 previousObjectRequest = objectRequest;
1504 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1509 [copyQueue addOperation:operation];
1516 #pragma mark ASIHTTPRequestDelegate
1518 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1519 NSOperationQueue *callbackQueue;
1520 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1521 if ([operationType isEqualToString:@"move"])
1522 callbackQueue = moveCallbackQueue;
1523 else if ([operationType isEqualToString:@"copy"])
1524 callbackQueue = copyCallbackQueue;
1525 else if ([operationType isEqualToString:@"delete"])
1526 callbackQueue = deleteCallbackQueue;
1527 else if ([operationType isEqualToString:@"upload"])
1528 callbackQueue = uploadCallbackQueue;
1529 else if ([operationType isEqualToString:@"download"])
1530 callbackQueue = downloadCallbackQueue;
1532 dispatch_async(dispatch_get_main_queue(), ^{
1533 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1534 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1538 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1539 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1540 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1542 operation.completionBlock = ^{
1544 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1545 dispatch_async(dispatch_get_main_queue(), ^{
1546 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1547 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1552 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1553 [callbackQueue addOperation:operation];
1556 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1557 if (request.isCancelled) {
1558 // Request has been cancelled
1559 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1560 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1561 withObject:request];
1563 NSOperationQueue *callbackQueue;
1564 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1565 if ([operationType isEqualToString:@"move"])
1566 callbackQueue = moveCallbackQueue;
1567 else if ([operationType isEqualToString:@"copy"])
1568 callbackQueue = copyCallbackQueue;
1569 else if ([operationType isEqualToString:@"delete"])
1570 callbackQueue = deleteCallbackQueue;
1571 else if ([operationType isEqualToString:@"upload"])
1572 callbackQueue = uploadCallbackQueue;
1573 else if ([operationType isEqualToString:@"download"])
1574 callbackQueue = downloadCallbackQueue;
1576 dispatch_async(dispatch_get_main_queue(), ^{
1577 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1578 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1582 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1583 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1584 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1586 operation.completionBlock = ^{
1588 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1589 dispatch_async(dispatch_get_main_queue(), ^{
1590 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1591 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1596 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1597 [callbackQueue addOperation:operation];
1601 - (void)requestFailed:(ASIPithosRequest *)request {
1603 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1604 DLog(@"Request failed: %@", request.url);
1605 if (operation.isCancelled)
1607 if (request.isCancelled) {
1608 dispatch_async(dispatch_get_main_queue(), ^{
1609 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1610 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1614 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1616 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1617 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1618 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1619 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1621 dispatch_async(dispatch_get_main_queue(), ^{
1622 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1623 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1625 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1626 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1628 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1633 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1635 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1636 DLog(@"Download finished: %@", objectRequest.url);
1637 if (operation.isCancelled) {
1638 [self requestFailed:objectRequest];
1639 } else if (objectRequest.responseStatusCode == 200) {
1640 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1641 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1642 NSUInteger totalBytes = activity.totalBytes;
1644 // XXX change contentLength to objectContentLength if it is fixed in the server
1645 if ([objectRequest contentLength] == 0) {
1646 // The check above was:
1647 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1648 // I checked for directory content types in order not to create a file in place of a directory,
1649 // but this callback method is not called in the case of a directory download.
1650 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1651 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1652 DLog(@"Downloaded 0 bytes");
1653 NSFileManager *fileManager = [NSFileManager defaultManager];
1654 if (![fileManager fileExistsAtPath:filePath]) {
1655 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1656 dispatch_async(dispatch_get_main_queue(), ^{
1657 NSAlert *alert = [[NSAlert alloc] init];
1658 [alert setMessageText:@"Create File Error"];
1659 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1660 [alert addButtonWithTitle:@"OK"];
1667 NSUInteger currentBytes = [objectRequest objectContentLength];
1668 if (currentBytes == 0)
1669 currentBytes = totalBytes;
1670 dispatch_async(dispatch_get_main_queue(), ^{
1671 [activityFacility endActivity:activity
1672 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1673 totalBytes:totalBytes
1674 currentBytes:currentBytes];
1677 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1678 [self requestFailed:objectRequest];
1683 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1685 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1686 DLog(@"Upload directory object finished: %@", objectRequest.url);
1687 if (operation.isCancelled) {
1688 [self requestFailed:objectRequest];
1689 } else if (objectRequest.responseStatusCode == 201) {
1690 DLog(@"Directory object created: %@", objectRequest.url);
1691 dispatch_async(dispatch_get_main_queue(), ^{
1692 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1693 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1694 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1695 [node forceRefresh];
1697 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1700 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1701 [self forceRefresh:self];
1702 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1703 [self refresh:self];
1706 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1707 [self requestFailed:objectRequest];
1712 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1714 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1715 DLog(@"Upload using hashmap finished: %@", objectRequest.url);
1716 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1717 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1718 NSUInteger totalBytes = activity.totalBytes;
1719 NSUInteger currentBytes = activity.currentBytes;
1720 if (operation.isCancelled) {
1721 [self requestFailed:objectRequest];
1722 } else if (objectRequest.responseStatusCode == 201) {
1723 DLog(@"Object created: %@", objectRequest.url);
1724 dispatch_async(dispatch_get_main_queue(), ^{
1725 [activityFacility endActivity:activity
1726 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1727 totalBytes:totalBytes
1728 currentBytes:totalBytes];
1729 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1730 [node forceRefresh];
1732 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1735 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1736 [self forceRefresh:self];
1737 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1738 [self refresh:self];
1740 } else if (objectRequest.responseStatusCode == 409) {
1741 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1742 if (iteration == 0) {
1743 DLog(@"Upload iteration limit reached: %@", objectRequest.url);
1744 dispatch_async(dispatch_get_main_queue(), ^{
1745 [activityFacility endActivity:activity
1746 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1747 NSAlert *alert = [[NSAlert alloc] init];
1748 [alert setMessageText:@"Upload Timeout"];
1749 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1750 [objectRequest.userInfo objectForKey:@"objectName"]]];
1751 [alert addButtonWithTitle:@"OK"];
1756 DLog(@"object is missing hashes: %@", objectRequest.url);
1757 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1758 withMissingHashes:[objectRequest hashes]];
1759 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1760 if (totalBytes >= [missingBlocks count]*blockSize)
1761 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1762 dispatch_async(dispatch_get_main_queue(), ^{
1763 [activityFacility updateActivity:activity
1764 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1765 totalBytes:totalBytes
1766 currentBytes:currentBytes];
1768 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1769 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1770 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1772 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1773 missingBlockIndex:missingBlockIndex
1774 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1775 newContainerRequest.delegate = self;
1776 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1777 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1778 newContainerRequest.userInfo = objectRequest.userInfo;
1779 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1780 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1781 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1782 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1783 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1784 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1785 [activityFacility updateActivity:activity
1786 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1787 totalBytes:activity.totalBytes
1788 currentBytes:(activity.currentBytes + size)];
1790 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1792 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1793 [self requestFailed:objectRequest];
1798 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1800 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1801 DLog(@"Upload of missing block finished: %@", containerRequest.url);
1802 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1803 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1804 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1805 if (operation.isCancelled) {
1806 [self requestFailed:containerRequest];
1807 } else if (containerRequest.responseStatusCode == 202) {
1808 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1809 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1810 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1811 if (missingBlockIndex == NSNotFound) {
1812 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1813 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1814 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1815 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1816 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1818 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1819 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1822 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1823 newObjectRequest.delegate = self;
1824 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1825 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1826 newObjectRequest.userInfo = containerRequest.userInfo;
1827 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1828 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1829 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1830 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1831 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1833 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1834 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1835 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1836 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1837 missingBlockIndex:missingBlockIndex
1838 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1839 newContainerRequest.delegate = self;
1840 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1841 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1842 newContainerRequest.userInfo = containerRequest.userInfo;
1843 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1844 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1845 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1846 [activityFacility updateActivity:activity
1847 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1848 totalBytes:activity.totalBytes
1849 currentBytes:(activity.currentBytes + size)];
1851 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1854 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1855 [self requestFailed:containerRequest];
1860 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1862 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1863 DLog(@"Move object finished: %@", objectRequest.url);
1864 if (operation.isCancelled) {
1865 [self requestFailed:objectRequest];
1866 } else if (objectRequest.responseStatusCode == 201) {
1867 dispatch_async(dispatch_get_main_queue(), ^{
1868 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1869 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1870 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1871 [node forceRefresh];
1873 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1876 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1877 [self forceRefresh:self];
1878 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1879 [self refresh:self];
1882 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1883 [self requestFailed:objectRequest];
1888 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1890 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1891 DLog(@"Copy object finished: %@", objectRequest.url);
1892 if (operation.isCancelled) {
1893 [self requestFailed:objectRequest];
1894 } else if (objectRequest.responseStatusCode == 201) {
1895 dispatch_async(dispatch_get_main_queue(), ^{
1896 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1897 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1898 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1899 [node forceRefresh];
1901 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1904 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1905 [self forceRefresh:self];
1906 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1907 [self refresh:self];
1910 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1911 [self requestFailed:objectRequest];
1916 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1918 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1919 DLog(@"Delete object finished: %@", objectRequest.url);
1920 if (operation.isCancelled) {
1921 [self requestFailed:objectRequest];
1922 } else if (objectRequest.responseStatusCode == 204) {
1923 dispatch_async(dispatch_get_main_queue(), ^{
1924 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1925 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1926 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1927 [node forceRefresh];
1929 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1932 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1933 [self forceRefresh:self];
1934 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1935 [self refresh:self];
1938 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1939 [self requestFailed:objectRequest];
1945 #pragma mark NSSplitViewDelegate
1947 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1948 if (splitView == verticalSplitView)
1951 return ([horizontalSplitView bounds].size.height - 108);
1954 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1955 if (splitView == verticalSplitView)
1958 return ([horizontalSplitView bounds].size.height - 108);
1961 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1962 if (splitView == verticalSplitView) {
1963 if (proposedPosition < 120)
1965 else if (proposedPosition > 220)
1968 return proposedPosition;
1970 return ([horizontalSplitView bounds].size.height - 108);
1975 #pragma mark NSOutlineViewDataSource
1977 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1978 if (!browserInitialized)
1982 if (item == containersNode)
1983 return containersNodeChildren.count;
1984 if (item == sharedNode)
1989 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1990 if (!browserInitialized)
1993 return (!index ? containersNode : sharedNode);
1994 if (item == sharedNode)
1995 return (!index ? mySharedNode : othersSharedNode);
1996 return [containersNodeChildren objectAtIndex:index];
1999 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2000 if ((item == containersNode) || (item == sharedNode))
2005 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2006 PithosNode *node = (PithosNode *)item;
2010 #pragma mark Drag and Drop destination
2012 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2013 validateDrop:(id<NSDraggingInfo>)info
2014 proposedItem:(id)item
2015 proposedChildIndex:(NSInteger)index {
2016 NSDragOperation result = NSDragOperationNone;
2017 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2019 PithosNode *dropNode = (PithosNode *)item;
2020 if ([dropNode class] != [PithosContainerNode class])
2022 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2023 result = NSDragOperationCopy;
2024 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2025 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2026 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2027 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2028 if (![dropNode isEqualTo:draggedParentNode])
2029 result = NSDragOperationMove;
2030 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2031 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2032 result = NSDragOperationCopy;
2038 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2039 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2040 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2041 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2042 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2043 PithosNode *node = (PithosNode *)item;
2044 DLog(@"drag in node: %@", node.url);
2045 return [self uploadFiles:filenames toNode:node];
2047 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2048 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2049 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2050 PithosNode *node = (PithosNode *)item;
2051 DLog(@"drag local node: %@", node.url);
2052 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2053 ([info draggingSourceOperationMask] & NSDragOperationMove))
2054 return [self moveNodes:draggedNodes toNode:node];
2055 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2056 return [self cpyNodes:draggedNodes toNode:node];
2063 #pragma mark NSOutlineViewDelegate
2065 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2066 if ((item == containersNode) || (item == sharedNode))
2071 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2072 if ((item == containersNode) || (item == sharedNode))
2077 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2078 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2081 [browser loadColumnZero];
2087 #pragma mark NSMenuDelegate
2089 - (void)menuNeedsUpdate:(NSMenu *)menu {
2090 [menu removeAllItems];
2091 NSMenuItem *menuItem;
2092 NSString *menuItemTitle;
2093 BOOL nodeContextMenu = NO;
2094 PithosNode *menuNode = nil;
2095 NSMutableArray *menuNodes;
2096 if (menu == browserMenu) {
2097 NSInteger column = [browser clickedColumn];
2098 NSInteger row = [browser clickedRow];
2099 if ((column == -1) || (row == -1)) {
2101 // General context menu
2102 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2103 if ([menuNodesIndexPaths count] == 0) {
2104 menuNode = [browser parentForItemsInColumn:0];
2105 } else if (([menuNodesIndexPaths count] != 1) ||
2106 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2107 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2109 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2112 menuNode = [browser parentForItemsInColumn:column];
2113 if ([menuNode class] == [PithosObjectNode class]) {
2114 // Node context menu
2115 menuNodes = [NSMutableArray arrayWithObject:menuNode];
2116 nodeContextMenu = YES;
2119 // General context menu
2122 // Node context menu
2123 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2124 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2125 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2126 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2127 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2128 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2131 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2133 nodeContextMenu = YES;
2135 } else if (menu == outlineViewMenu) {
2136 NSInteger row = [outlineView clickedRow];
2138 row = [outlineView selectedRow];
2141 menuNode = [outlineView itemAtRow:row];
2144 if (!nodeContextMenu) {
2145 // General context menu
2146 if (([menuNode class] == [PithosAccountNode class]) ||
2147 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2148 ([menuNode class] == [PithosEmptyNode class]))
2151 if (!menuNode.shared && !menuNode.sharingAccount) {
2152 menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""];
2153 [menuItem setRepresentedObject:menuNode];
2154 [menu addItem:menuItem];
2155 [menu addItem:[NSMenuItem separatorItem]];
2158 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2159 [menu addItem:menuItem];
2160 [menu addItem:[NSMenuItem separatorItem]];
2162 menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2163 action:@selector(menuGetInfo:)
2165 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2166 [menu addItem:menuItem];
2168 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2169 (([menuNode class] == [PithosContainerNode class]) ||
2170 (([menuNode class] == [PithosSubdirNode class]) &&
2171 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2172 NSUInteger clipboardNodesCount = [clipboardNodes count];
2173 if (clipboardNodesCount == 0) {
2174 self.clipboardNodes = nil;
2175 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2176 if (clipboardNodesCount == 1)
2177 menuItemTitle = @"Paste Item";
2179 menuItemTitle = @"Paste Items";
2180 [menu addItem:[NSMenuItem separatorItem]];
2181 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2182 [menuItem setRepresentedObject:menuNode];
2183 [menu addItem:menuItem];
2187 // Node context menu
2188 NSUInteger menuNodesCount = [menuNodes count];
2189 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2191 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2192 menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""];
2193 [menuItem setRepresentedObject:menuNodes];
2194 [menu addItem:menuItem];
2195 [menu addItem:[NSMenuItem separatorItem]];
2197 // Move to Trash (pithos container only)
2199 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2200 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2201 menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2202 action:@selector(menuMoveToTrash:)
2204 [menuItem setRepresentedObject:menuNodes];
2205 [menu addItem:menuItem];
2207 menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""];
2208 [menuItem setRepresentedObject:menuNodes];
2209 [menu addItem:menuItem];
2210 [menu addItem:[NSMenuItem separatorItem]];
2213 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2214 [menu addItem:menuItem];
2216 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2217 [menu addItem:[NSMenuItem separatorItem]];
2218 menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2219 action:@selector(menuGetInfo:)
2221 [menuItem setRepresentedObject:menuNodes];
2222 [menu addItem:menuItem];
2224 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2225 [menu addItem:[NSMenuItem separatorItem]];
2228 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2229 if (menuNodesCount == 1)
2230 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2232 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2233 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""];
2234 [menuItem setRepresentedObject:menuNodes];
2235 [menu addItem:menuItem];
2238 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2239 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2240 if (menuNodesCount == 1)
2241 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2243 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2244 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""];
2245 [menuItem setRepresentedObject:menuNodes];
2246 [menu addItem:menuItem];
2249 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2250 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2251 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2252 NSUInteger clipboardNodesCount = [clipboardNodes count];
2253 if (clipboardNodesCount == 0) {
2254 self.clipboardNodes = nil;
2255 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2256 if (clipboardNodesCount == 1)
2257 menuItemTitle = @"Paste Item";
2259 menuItemTitle = @"Paste Items";
2260 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2261 [menuItem setRepresentedObject:firstMenuNode];
2262 [menu addItem:menuItem];
2269 #pragma mark NSMenuValidation
2271 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2272 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2273 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2274 if ([menuNodesIndexPaths count] == 0)
2277 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2278 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2279 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2280 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2281 ((menuItem.action == @selector(delete:)) &&
2282 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2283 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2286 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2287 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2288 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2290 menuItem.representedObject = menuNodes;
2291 } else if (menuItem.action == @selector(paste:)) {
2292 if (!clipboardNodes || ![clipboardNodes count])
2295 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2296 PithosNode *menuNode;
2297 if ([menuNodesIndexPaths count] == 0)
2298 menuNode = [browser parentForItemsInColumn:0];
2299 else if (([menuNodesIndexPaths count] != 1) ||
2300 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2301 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2303 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2305 if (menuNode.shared || menuNode.sharingAccount ||
2306 (([menuNode class] != [PithosContainerNode class]) &&
2307 (([menuNode class] != [PithosSubdirNode class]) ||
2308 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2309 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2312 menuItem.representedObject = menuNode;
2317 - (void)cut:(NSMenuItem *)sender {
2318 [self menuCut:sender];
2321 - (void)copy:(NSMenuItem *)sender {
2322 [self menuCopy:sender];
2325 - (void)paste:(NSMenuItem *)sender {
2326 [self menuPaste:sender];
2329 - (void)delete:(NSMenuItem *)sender {
2330 if (sender.tag == 0)
2331 [self menuMoveToTrash:sender];
2333 [self menuDelete:sender];
2337 #pragma mark Menu Actions
2339 - (void)menuNewFolder:(NSMenuItem *)sender {
2340 PithosNode *node = (PithosNode *)[sender representedObject];
2341 if ([node class] == [PithosContainerNode class]) {
2342 // Operation: Create (upload) a new root application/directory object
2343 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2345 if (operation.isCancelled)
2347 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2348 containerName:node.pithosContainer.name
2349 subdirName:@"untitled folder"];
2350 NSString *fileName = [safeObjectName lastPathComponent];
2351 if (operation.isCancelled)
2353 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2354 containerName:node.pithosContainer.name
2355 objectName:safeObjectName
2357 contentType:@"application/directory"
2359 contentDisposition:nil
2362 isPublic:ASIPithosObjectRequestPublicIgnore
2364 data:[NSData data]];
2365 objectRequest.delegate = self;
2366 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2367 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2368 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2369 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2370 message:messagePrefix];
2371 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2372 fileName, @"fileName",
2373 [NSArray arrayWithObject:node], @"refreshNodes",
2374 [NSNumber numberWithBool:YES], @"refresh",
2375 activity, @"activity",
2376 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2377 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2378 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2379 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2380 [NSNumber numberWithUnsignedInteger:10], @"retries",
2381 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2382 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2383 uploadNetworkQueue, @"networkQueue",
2384 @"upload", @"operationType",
2386 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2389 [uploadQueue addOperation:operation];
2390 } else if (([node class] == [PithosSubdirNode class]) &&
2391 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2392 // Operation: Create (upload) a new aplication/directory object
2393 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2395 if (operation.isCancelled)
2397 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2398 containerName:node.pithosContainer.name
2399 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2400 NSString *fileName = [safeObjectName lastPathComponent];
2401 if (operation.isCancelled)
2403 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2404 containerName:node.pithosContainer.name
2405 objectName:safeObjectName
2407 contentType:@"application/directory"
2409 contentDisposition:nil
2412 isPublic:ASIPithosObjectRequestPublicIgnore
2414 data:[NSData data]];
2415 objectRequest.delegate = self;
2416 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2417 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2418 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2419 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2420 message:messagePrefix];
2421 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2422 fileName, @"fileName",
2423 [NSArray arrayWithObject:node], @"refreshNodes",
2424 [NSNumber numberWithBool:YES], @"refresh",
2425 activity, @"activity",
2426 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2427 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2428 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2429 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2430 [NSNumber numberWithUnsignedInteger:10], @"retries",
2431 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2432 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2433 uploadNetworkQueue, @"networkQueue",
2434 @"upload", @"operationType",
2436 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2439 [uploadQueue addOperation:operation];
2443 - (void)menuGetInfo:(NSMenuItem *)sender {
2444 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2445 [node showPithosNodeInfo:sender];
2449 - (void)menuDownload:(NSMenuItem *)sender {
2450 NSArray *nodes = (NSArray *)[sender representedObject];
2451 PithosNode *firstNode = [nodes objectAtIndex:0];
2452 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2453 NSSavePanel *save = [NSSavePanel savePanel];
2454 save.nameFieldStringValue = firstNode.displayName;
2455 NSInteger result = [save runModal];
2456 if (result == NSOKButton) {
2457 NSString *destinationPath = save.URL.path;
2458 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2459 NSString *newFileName = [destinationPath lastPathComponent];
2460 if ([destinationPath hasSuffix:@"/"])
2461 newFileName = [newFileName stringByAppendingString:@"/"];
2462 if ([firstNode.displayName isEqualToString:newFileName])
2464 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2467 NSOpenPanel *open = [NSOpenPanel openPanel];
2468 open.canChooseFiles = NO;
2469 open.canChooseDirectories = YES;
2470 open.canCreateDirectories = YES;
2471 NSInteger result = [open runModal];
2472 if (result == NSOKButton) {
2473 NSString *directoryPath = open.URL.path;
2474 for (PithosNode *node in nodes) {
2475 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2481 - (void)menuDelete:(NSMenuItem *)sender {
2482 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2483 if (([node class] == [PithosObjectNode class]) ||
2484 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2485 // Operation: Delete an object or subdir/ node
2486 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2488 if (operation.isCancelled)
2490 NSString *fileName = [node.pithosObject.name lastPathComponent];
2491 if ([node.pithosObject.name hasSuffix:@"/"])
2492 fileName = [fileName stringByAppendingString:@"/"];
2493 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2494 containerName:node.pithosContainer.name
2495 objectName:node.pithosObject.name];
2496 if (operation.isCancelled)
2498 objectRequest.delegate = self;
2499 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2500 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2501 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2502 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2503 message:messagePrefix];
2504 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2505 fileName, @"fileName",
2506 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2507 activity, @"activity",
2508 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2509 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2510 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2511 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2512 [NSNumber numberWithUnsignedInteger:10], @"retries",
2513 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2514 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2515 deleteNetworkQueue, @"networkQueue",
2516 @"delete", @"operationType",
2518 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2521 [deleteQueue addOperation:operation];
2522 } else if ([node class] == [PithosSubdirNode class]) {
2523 // Operation: Delete a subdir node and its descendants
2524 // The resulting ASIPithosObjectRequests are chained through dependencies
2525 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2527 if (operation.isCancelled)
2529 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2530 containerName:node.pithosContainer.name
2531 objectName:node.pithosObject.name];
2532 if (!operation.isCancelled && objectRequests) {
2533 ASIPithosObjectRequest *previousObjectRequest = nil;
2534 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2535 if (operation.isCancelled)
2537 objectRequest.delegate = self;
2538 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2539 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2540 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2541 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2542 message:messagePrefix];
2543 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2544 [NSDictionary dictionaryWithObjectsAndKeys:
2545 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2546 activity, @"activity",
2547 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2548 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2549 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2550 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2551 [NSNumber numberWithUnsignedInteger:10], @"retries",
2552 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2553 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2554 deleteNetworkQueue, @"networkQueue",
2555 @"delete", @"operationType",
2557 if (previousObjectRequest)
2558 [objectRequest addDependency:previousObjectRequest];
2559 previousObjectRequest = objectRequest;
2560 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2565 [deleteQueue addOperation:operation];
2570 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2571 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2572 if (([node class] == [PithosObjectNode class]) ||
2573 (([node class] == [PithosSubdirNode class]) &&
2574 !node.pithosObject.subdir &&
2575 [node.pithosObject.name hasSuffix:@"/"])) {
2576 // Operation: Move to trash an object or subdir/ node
2577 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2579 if (operation.isCancelled)
2581 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2582 containerName:@"trash"
2583 objectName:node.pithosObject.name];
2584 if (!operation.isCancelled && safeObjectName) {
2585 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2586 containerName:node.pithosContainer.name
2587 objectName:node.pithosObject.name
2588 destinationContainerName:@"trash"
2589 destinationObjectName:safeObjectName
2591 if (!operation.isCancelled && objectRequest) {
2592 objectRequest.delegate = self;
2593 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2594 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2595 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2596 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2597 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2598 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2599 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2600 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2601 message:messagePrefix];
2602 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2603 [NSDictionary dictionaryWithObjectsAndKeys:
2604 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2605 activity, @"activity",
2606 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2607 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2608 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2609 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2610 [NSNumber numberWithUnsignedInteger:10], @"retries",
2611 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2612 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2613 moveNetworkQueue, @"networkQueue",
2614 @"move", @"operationType",
2616 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2621 [moveQueue addOperation:operation];
2622 } else if ([node class] == [PithosSubdirNode class]) {
2623 // Operation: Move to trash a subdir node and its descendants
2624 // The resulting ASIPithosObjectRequests are chained through dependencies
2625 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2627 if (operation.isCancelled)
2629 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2630 containerName:@"trash"
2631 subdirName:node.pithosObject.name];
2632 if (!operation.isCancelled && safeObjectName) {
2633 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2634 containerName:node.pithosContainer.name
2635 objectName:node.pithosObject.name
2636 destinationContainerName:@"trash"
2637 destinationObjectName:safeObjectName
2639 if (!operation.isCancelled && objectRequests) {
2640 ASIPithosObjectRequest *previousObjectRequest = nil;
2641 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2642 if (operation.isCancelled)
2644 objectRequest.delegate = self;
2645 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2646 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2647 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2648 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2649 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2650 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2651 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2652 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2653 message:messagePrefix];
2654 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2655 [NSDictionary dictionaryWithObjectsAndKeys:
2656 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2657 activity, @"activity",
2658 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2659 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2660 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2661 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2662 [NSNumber numberWithUnsignedInteger:10], @"retries",
2663 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2664 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2665 moveNetworkQueue, @"networkQueue",
2666 @"move", @"operationType",
2668 if (previousObjectRequest)
2669 [objectRequest addDependency:previousObjectRequest];
2670 previousObjectRequest = objectRequest;
2671 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2677 [moveQueue addOperation:operation];
2682 - (void)menuCut:(NSMenuItem *)sender {
2683 self.clipboardNodes = (NSArray *)[sender representedObject];
2684 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2685 self.clipboardCopy = NO;
2688 - (void)menuCopy:(NSMenuItem *)sender {
2689 self.clipboardNodes = (NSArray *)[sender representedObject];
2690 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2691 self.clipboardCopy = YES;
2694 - (void)menuPaste:(NSMenuItem *)sender {
2695 if (!clipboardNodes || ![clipboardNodes count])
2697 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2698 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2699 if (clipboardCopy) {
2700 [self cpyNodes:localClipboardNodes toNode:dropNode];
2701 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2702 self.clipboardNodes = nil;
2703 self.clipboardParentNode = nil;
2704 [self moveNodes:localClipboardNodes toNode:dropNode];
2709 #pragma mark PithosActivityFacilityDelegate
2711 - (void)activityUpdate:(NSDictionary *)info {
2712 NSString *message = [info objectForKey:@"message"];
2713 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2714 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2715 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2716 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2717 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2718 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2719 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2720 if (runningActivitiesCount && totalBytes) {
2721 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2722 [activityProgressIndicator startAnimation:self];
2724 [activityProgressIndicator setDoubleValue:1.0];
2725 [activityProgressIndicator stopAnimation:self];
2729 message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
2730 [activityTextField setStringValue:message];