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 draggedNodes, draggedParentNode;
114 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
117 #pragma Object Lifecycle
120 return [super initWithWindowNibName:@"PithosBrowserController"];
123 - (void)windowDidLoad {
124 [super windowDidLoad];
125 if (browser && !browserInitialized) {
126 browserInitialized = YES;
131 - (void)initBrowser {
132 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
133 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
134 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
136 [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
137 [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
138 [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
140 [browser setCellClass:[PithosBrowserCell class]];
141 [browser setAllowsBranchSelection:YES];
142 [browser setAllowsMultipleSelection:YES];
143 [browser setAllowsEmptySelection:YES];
144 [browser setAllowsTypeSelect:YES];
145 [browser setDoubleAction:@selector(browserDoubleAction:)];
147 moveNetworkQueue = [[ASINetworkQueue alloc] init];
148 moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
149 // moveNetworkQueue.maxConcurrentOperationCount = 1;
150 copyNetworkQueue = [[ASINetworkQueue alloc] init];
151 copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
152 // copyNetworkQueue.maxConcurrentOperationCount = 1;
153 deleteNetworkQueue = [[ASINetworkQueue alloc] init];
154 deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
155 // deleteNetworkQueue.maxConcurrentOperationCount = 1;
156 uploadNetworkQueue = [[ASINetworkQueue alloc] init];
157 uploadNetworkQueue.showAccurateProgress = YES;
158 uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
159 // uploadNetworkQueue.maxConcurrentOperationCount = 1;
160 downloadNetworkQueue = [[ASINetworkQueue alloc] init];
161 downloadNetworkQueue.showAccurateProgress = YES;
162 downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
163 // downloadNetworkQueue.maxConcurrentOperationCount = 1;
165 moveQueue = [[NSOperationQueue alloc] init];
166 [moveQueue setSuspended:YES];
167 moveQueue.name = @"gr.grnet.pithos.MoveQueue";
168 // moveQueue.maxConcurrentOperationCount = 1;
169 copyQueue = [[NSOperationQueue alloc] init];
170 [copyQueue setSuspended:YES];
171 copyQueue.name = @"gr.grnet.pithos.CopyQueue";
172 // copyQueue.maxConcurrentOperationCount = 1;
173 deleteQueue = [[NSOperationQueue alloc] init];
174 [deleteQueue setSuspended:YES];
175 deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
176 // deleteQueue.maxConcurrentOperationCount = 1;
177 uploadQueue = [[NSOperationQueue alloc] init];
178 [uploadQueue setSuspended:YES];
179 uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
180 // uploadQueue.maxConcurrentOperationCount = 1;
181 downloadQueue = [[NSOperationQueue alloc] init];
182 [downloadQueue setSuspended:YES];
183 downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
184 // downloadQueue.maxConcurrentOperationCount = 1;
186 moveCallbackQueue = [[NSOperationQueue alloc] init];
187 [moveCallbackQueue setSuspended:YES];
188 moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
189 // moveCallbackQueue.maxConcurrentOperationCount = 1;
190 copyCallbackQueue = [[NSOperationQueue alloc] init];
191 [copyCallbackQueue setSuspended:YES];
192 copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
193 // copyCallbackQueue.maxConcurrentOperationCount = 1;
194 deleteCallbackQueue = [[NSOperationQueue alloc] init];
195 [deleteCallbackQueue setSuspended:YES];
196 deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
197 // deleteCallbackQueue.maxConcurrentOperationCount = 1;
198 uploadCallbackQueue = [[NSOperationQueue alloc] init];
199 [uploadCallbackQueue setSuspended:YES];
200 uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
201 // uploadCallbackQueue.maxConcurrentOperationCount = 1;
202 downloadCallbackQueue = [[NSOperationQueue alloc] init];
203 [downloadCallbackQueue setSuspended:YES];
204 downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
205 // downloadCallbackQueue.maxConcurrentOperationCount = 1;
207 [activityProgressIndicator setUsesThreadedAnimation:YES];
208 [activityProgressIndicator setMinValue:0.0];
209 [activityProgressIndicator setMaxValue:1.0];
210 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
212 self.accountNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
213 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
214 containersNodeChildren = [[NSMutableArray alloc] init];
215 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
216 mySharedNode = [[PithosAccountNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
217 mySharedNode.displayName = @"shared by me";
218 mySharedNode.shared = YES;
219 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
220 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithosAccountManager:pithosAccountManager andPithos:pithos];
221 othersSharedNode.displayName = @"shared with me";
222 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
224 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[PithosOutlineViewCell alloc] init]];
226 // Register for updates
227 // PithosAccountNode accountNode updates outlineView container nodes
228 [[NSNotificationCenter defaultCenter] addObserver:self
229 selector:@selector(pithosAccountNodeChildrenUpdated:)
230 name:@"PithosNodeChildrenUpdated"
232 // PithosNode updates browser nodes
233 [[NSNotificationCenter defaultCenter] addObserver:self
234 selector:@selector(pithosNodeChildrenUpdated:)
235 name:@"PithosNodeChildrenUpdated"
237 // Request for browser refresh
238 [[NSNotificationCenter defaultCenter] addObserver:self
239 selector:@selector(pithosBrowserRefreshNeeded:)
240 name:@"PithosBrowserRefreshNeeded"
244 - (void)resetBrowser {
245 @synchronized(self) {
250 [refreshTimer invalidate];
252 [moveNetworkQueue reset];
253 [copyNetworkQueue reset];
254 [deleteNetworkQueue reset];
255 [uploadNetworkQueue reset];
256 [downloadNetworkQueue reset];
258 [moveQueue cancelAllOperations];
259 [moveQueue setSuspended:YES];
260 [copyQueue cancelAllOperations];
261 [copyQueue setSuspended:YES];
262 [deleteQueue cancelAllOperations];
263 [deleteQueue setSuspended:YES];
264 [uploadQueue cancelAllOperations];
265 [uploadQueue setSuspended:YES];
266 [downloadQueue cancelAllOperations];
267 [downloadQueue setSuspended:YES];
269 [moveCallbackQueue cancelAllOperations];
270 [moveCallbackQueue setSuspended:YES];
271 [copyCallbackQueue cancelAllOperations];
272 [copyCallbackQueue setSuspended:YES];
273 [deleteCallbackQueue cancelAllOperations];
274 [deleteCallbackQueue setSuspended:YES];
275 [uploadCallbackQueue cancelAllOperations];
276 [uploadCallbackQueue setSuspended:YES];
277 [downloadCallbackQueue cancelAllOperations];
278 [downloadCallbackQueue setSuspended:YES];
280 [accountNode pithosNodeWillBeRemoved];
281 [mySharedNode pithosNodeWillBeRemoved];
282 [othersSharedNode pithosNodeWillBeRemoved];
285 [browser loadColumnZero];
286 [containersNodeChildren removeAllObjects];
287 [outlineView reloadData];
288 // Expand the folder outline view
289 [outlineView expandItem:nil expandChildren:YES];
290 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
292 activityFacility.delegate = nil;
293 [activityProgressIndicator setDoubleValue:1.0];
294 [activityProgressIndicator stopAnimation:self];
296 @synchronized(self) {
301 - (void)startBrowser {
302 @synchronized(self) {
307 // In the improbable case of leftover operations
308 [moveNetworkQueue reset];
309 [copyNetworkQueue reset];
310 [deleteNetworkQueue reset];
311 [uploadNetworkQueue reset];
312 [downloadNetworkQueue reset];
313 [moveQueue cancelAllOperations];
314 [copyQueue cancelAllOperations];
315 [deleteQueue cancelAllOperations];
316 [uploadQueue cancelAllOperations];
317 [downloadQueue cancelAllOperations];
318 [moveCallbackQueue cancelAllOperations];
319 [copyCallbackQueue cancelAllOperations];
320 [deleteCallbackQueue cancelAllOperations];
321 [uploadCallbackQueue cancelAllOperations];
322 [downloadCallbackQueue cancelAllOperations];
324 [moveNetworkQueue go];
325 [copyNetworkQueue go];
326 [deleteNetworkQueue go];
327 [uploadNetworkQueue go];
328 [downloadNetworkQueue go];
329 [moveQueue setSuspended:NO];
330 [copyQueue setSuspended:NO];
331 [deleteQueue setSuspended:NO];
332 [uploadQueue setSuspended:NO];
333 [downloadQueue setSuspended:NO];
334 [moveCallbackQueue setSuspended:NO];
335 [copyCallbackQueue setSuspended:NO];
336 [deleteCallbackQueue setSuspended:NO];
337 [uploadCallbackQueue setSuspended:NO];
338 [downloadCallbackQueue setSuspended:NO];
340 accountNode.pithos = pithos;
341 accountNode.pithosAccountManager = pithosAccountManager;
342 [accountNode forceRefresh];
343 mySharedNode.pithos = pithos;
344 mySharedNode.pithosAccountManager = pithosAccountManager;
345 [mySharedNode forceRefresh];
346 othersSharedNode.pithos = pithos;
347 othersSharedNode.pithosAccountManager = pithosAccountManager;
348 [othersSharedNode forceRefresh];
350 // [activityFacility reset];
351 activityFacility.delegate = self;
353 refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
355 selector:@selector(forceRefresh:)
358 @synchronized(self) {
363 - (BOOL)operationsPending {
364 return ([moveNetworkQueue operationCount] ||
365 [copyNetworkQueue operationCount] ||
366 [deleteNetworkQueue operationCount] ||
367 [uploadNetworkQueue operationCount] ||
368 [downloadNetworkQueue operationCount] ||
369 [moveQueue operationCount] ||
370 [copyQueue operationCount] ||
371 [deleteQueue operationCount] ||
372 [uploadQueue operationCount] ||
373 [downloadQueue operationCount] ||
374 [moveCallbackQueue operationCount] ||
375 [copyCallbackQueue operationCount] ||
376 [deleteCallbackQueue operationCount] ||
377 [uploadCallbackQueue operationCount] ||
378 [downloadCallbackQueue operationCount]);
382 [[NSNotificationCenter defaultCenter] removeObserver:self];
386 - (void)setPithos:(ASIPithos *)aPithos {
388 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
389 ![aPithos.authToken isEqualToString:pithos.authToken] ||
390 (aPithos.ignoreSSLErrors != pithos.ignoreSSLErrors) ||
391 ![aPithos.storageURLPrefix isEqual:pithos.storageURLPrefix] ||
392 ![aPithos.publicURLPrefix isEqual:pithos.publicURLPrefix]) {
404 #pragma mark Observers
406 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
407 if (![NSThread isMainThread]) {
408 [self performSelectorOnMainThread:@selector(pithosNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
411 PithosNode *node = (PithosNode *)[notification object];
412 if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
414 DLog(@"pithosNodeChildrenUpdated:%@", node.url);
415 NSInteger lastColumn = [browser lastColumn];
416 for (NSInteger column = lastColumn; column >= 0; column--) {
417 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
418 [browser reloadColumn:column];
424 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
425 if (![NSThread isMainThread]) {
426 [self performSelectorOnMainThread:@selector(pithosAccountNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
429 BOOL containerPithosFound = NO;
430 BOOL containerTrashFound = NO;
431 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
432 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
433 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
434 [removedContainersNodeChildren addIndex:i];
436 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
437 for (PithosContainerNode *containerNode in accountNode.children) {
438 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
439 if (![containersNodeChildren containsObject:containerNode])
440 [containersNodeChildren insertObject:containerNode atIndex:0];
441 containerPithosFound = YES;
442 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
443 NSUInteger insertIndex = 1;
444 if (!containerPithosFound)
446 if (![containersNodeChildren containsObject:containerNode])
447 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
448 containerTrashFound = YES;
449 } else if (![containersNodeChildren containsObject:containerNode]) {
450 [containersNodeChildren addObject:containerNode];
453 BOOL refreshAccountNode = NO;
454 if (!containerPithosFound) {
455 // Create pithos node
456 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
457 containerName:@"pithos"];
458 [PithosUtilities startAndWaitForRequest:containerRequest];
459 if ([containerRequest error]) {
460 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
462 refreshAccountNode = YES;
465 if (!containerTrashFound) {
467 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
468 containerName:@"trash"];
469 [PithosUtilities startAndWaitForRequest:containerRequest];
470 if ([containerRequest error]) {
471 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
473 refreshAccountNode = YES;
477 if (refreshAccountNode)
478 [accountNode refresh];
480 [outlineView reloadData];
482 // Expand the folder outline view
483 [outlineView expandItem:nil expandChildren:YES];
485 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
486 rootNode = [containersNodeChildren objectAtIndex:0];
487 [browser loadColumnZero];
494 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
501 - (IBAction)forceRefresh:(id)sender {
502 if (![NSThread isMainThread]) {
503 [self performSelectorOnMainThread:@selector(forceRefresh:) withObject:sender waitUntilDone:NO];
509 [accountNode forceRefresh];
510 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
511 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
512 node.forcedRefresh = YES;
513 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
515 [browser validateVisibleColumns];
518 - (IBAction)refresh:(id)sender {
519 if (![NSThread isMainThread]) {
520 [self performSelectorOnMainThread:@selector(refresh:) withObject:sender waitUntilDone:NO];
525 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
526 [self forceRefresh:sender];
529 [accountNode refresh];
530 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
531 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
533 [browser validateVisibleColumns];
538 #pragma mark NSBrowserDelegate
540 - (id)rootItemForBrowser:(NSBrowser *)browser {
544 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
545 PithosNode *node = (PithosNode *)item;
546 return node.children.count;
549 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
550 PithosNode *node = (PithosNode *)item;
551 return [node.children objectAtIndex:index];
554 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
555 PithosNode *node = (PithosNode *)item;
556 return node.isLeafItem;
559 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
560 PithosNode *node = (PithosNode *)item;
564 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
565 if (sharedPreviewController == nil)
566 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
567 return sharedPreviewController;
570 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
571 // if (!forUserResize) {
572 // id item = [browser parentForItemsInColumn:columnIndex];
573 // if ([self browser:browser isLeafItem:item]) {
574 // suggestedWidth = 200;
577 // return suggestedWidth;
580 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
586 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
587 PithosNode *node = (PithosNode *)item;
588 if (node.shared || node.sharingAccount ||
589 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
595 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
597 PithosNode *node = (PithosNode *)item;
598 NSString *newName = (NSString *)object;
599 NSUInteger newNameLength = [newName length];
600 NSRange firstSlashRange = [newName rangeOfString:@"/"];
601 if ((newNameLength == 0) ||
602 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
603 ([newName isEqualToString:node.displayName])) {
606 if (([node class] == [PithosObjectNode class]) ||
607 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
608 // Operation: Rename (move) an object or subdir/ node
609 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
611 if (operation.isCancelled)
613 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
614 if ([newName hasSuffix:@"/"])
615 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
616 NSError *error = nil;
618 if ([PithosUtilities objectExistsAtPithos:pithos
619 containerName:node.pithosContainer.name
620 objectName:destinationObjectName
622 isDirectory:&isDirectory
623 sharingAccount:nil]) {
624 dispatch_async(dispatch_get_main_queue(), ^{
625 NSAlert *alert = [[NSAlert alloc] init];
626 [alert setMessageText:@"Name Taken"];
627 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
628 [alert addButtonWithTitle:@"OK"];
635 if (operation.isCancelled)
637 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
638 containerName:node.pithosContainer.name
639 objectName:node.pithosObject.name
640 destinationContainerName:node.pithosContainer.name
641 destinationObjectName:destinationObjectName
643 if (!operation.isCancelled && objectRequest) {
644 objectRequest.delegate = self;
645 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
646 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
647 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
648 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
649 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
650 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
651 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
652 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
653 message:messagePrefix];
654 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
655 [NSDictionary dictionaryWithObjectsAndKeys:
656 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
657 [NSNumber numberWithBool:YES], @"refresh",
658 activity, @"activity",
659 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
660 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
661 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
662 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
663 [NSNumber numberWithUnsignedInteger:10], @"retries",
664 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
665 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
666 moveNetworkQueue, @"networkQueue",
667 @"move", @"operationType",
669 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
673 [moveQueue addOperation:operation];
674 } else if ([node class] == [PithosSubdirNode class]) {
675 if (firstSlashRange.length == 1)
677 // Operation: Rename (move) a subdir node and its descendants
678 // The resulting ASIPithosObjectRequests are chained through dependencies
679 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
681 if (operation.isCancelled)
683 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
684 NSError *error = nil;
686 if ([PithosUtilities objectExistsAtPithos:pithos
687 containerName:node.pithosContainer.name
688 objectName:destinationObjectName
690 isDirectory:&isDirectory
691 sharingAccount:nil]) {
692 dispatch_async(dispatch_get_main_queue(), ^{
693 NSAlert *alert = [[NSAlert alloc] init];
694 [alert setMessageText:@"Name Taken"];
695 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
696 [alert addButtonWithTitle:@"OK"];
703 if (operation.isCancelled)
705 if (node.pithosObject.subdir)
706 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
707 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
708 containerName:node.pithosContainer.name
709 objectName:node.pithosObject.name
710 destinationContainerName:node.pithosContainer.name
711 destinationObjectName:destinationObjectName
713 if (!operation.isCancelled && objectRequests) {
714 ASIPithosObjectRequest *previousObjectRequest = nil;
715 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
716 if (operation.isCancelled)
718 objectRequest.delegate = self;
719 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
720 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
721 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
722 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
723 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
724 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
725 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
726 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
727 message:messagePrefix];
728 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
729 [NSDictionary dictionaryWithObjectsAndKeys:
730 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
731 [NSNumber numberWithBool:YES], @"refresh",
732 activity, @"activity",
733 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
734 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
735 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
736 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
737 [NSNumber numberWithUnsignedInteger:10], @"retries",
738 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
739 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
740 moveNetworkQueue, @"networkQueue",
741 @"move", @"operationType",
743 if (previousObjectRequest)
744 [objectRequest addDependency:previousObjectRequest];
745 previousObjectRequest = objectRequest;
746 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
751 [moveQueue addOperation:operation];
755 #pragma mark Drag and Drop source
757 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
758 withEvent:(NSEvent *)event {
759 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
760 __block BOOL result = YES;
761 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
762 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
763 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
771 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
772 toPasteboard:(NSPasteboard *)pasteboard {
773 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
774 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
775 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
776 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
777 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
778 [propertyList addObject:[node.pithosObject.name pathExtension]];
779 [nodes addObject:node];
782 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
783 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
784 self.draggedNodes = nodes;
785 self.draggedParentNode = [browser parentForItemsInColumn:column];
789 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
790 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
791 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
792 for (PithosNode *node in draggedNodes) {
793 [names addObject:node.displayName];
794 // If the node is a subdir ask if the whole tree should be downloaded
795 if ([node class] == [PithosSubdirNode class]) {
796 NSAlert *alert = [[NSAlert alloc] init];
797 [alert setMessageText:@"Download directory"];
798 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
799 [alert addButtonWithTitle:@"OK"];
800 [alert addButtonWithTitle:@"Cancel"];
801 NSInteger choice = [alert runModal];
802 if (choice == NSAlertFirstButtonReturn)
803 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
805 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
811 #pragma mark Drag and Drop destination
813 - (NSDragOperation)browser:aBrowser
814 validateDrop:(id<NSDraggingInfo>)info
815 proposedRow:(NSInteger *)row
816 column:(NSInteger *)column
817 dropOperation:(NSBrowserDropOperation *)dropOperation {
818 NSDragOperation result = NSDragOperationNone;
819 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
820 // For a drop above, the drop is redirected to the parent item
821 if (*dropOperation == NSBrowserDropAbove)
823 // Only allow dropping in folders
825 PithosNode *dropNode;
827 // Check if the node is not a folder and if so redirect to the parent item
828 dropNode = [browser itemAtRow:*row inColumn:*column];
829 if ([dropNode class] == [PithosObjectNode class])
833 dropNode = [browser parentForItemsInColumn:*column];
835 if (!dropNode.shared &&
836 (!dropNode.sharingAccount ||
837 ([dropNode class] == [PithosSubdirNode class]) ||
838 ([dropNode class] == [PithosContainerNode class]))) {
839 *dropOperation = NSBrowserDropOn;
840 result = NSDragOperationCopy;
843 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
844 // For a drop above, the drop is redirected to the parent item
845 if (*dropOperation == NSBrowserDropAbove)
847 // Only allow dropping in folders
849 PithosNode *dropNode;
851 // Check if the node is not a folder and if so redirect to the parent item
852 dropNode = [browser itemAtRow:*row inColumn:*column];
853 if ([dropNode class] == [PithosObjectNode class])
857 dropNode = [browser parentForItemsInColumn:*column];
859 if (!dropNode.shared && !dropNode.sharingAccount) {
860 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
861 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
862 if ((([dropNode class] == [PithosContainerNode class]) ||
863 dropNode.pithosObject.subdir ||
864 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
865 ![dropNode isEqualTo:draggedParentNode]) {
866 // ![dropNode isEqualTo:draggedParentNode] &&
867 // ![draggedNodes containsObject:dropNode]) {
868 result = NSDragOperationMove;
870 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
871 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
872 if (([dropNode class] == [PithosContainerNode class]) ||
873 dropNode.pithosObject.subdir ||
874 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
875 result = NSDragOperationCopy;
884 - (BOOL)browser:(NSBrowser *)aBrowser
885 acceptDrop:(id<NSDraggingInfo>)info
887 column:(NSInteger)column
888 dropOperation:(NSBrowserDropOperation)dropOperation {
889 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
890 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
891 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
892 if ((column != -1) && (filenames != nil)) {
895 node = [browser itemAtRow:row inColumn:column];
897 node = [browser parentForItemsInColumn:column];
898 DLog(@"drag in node: %@", node.url);
899 return [self uploadFiles:filenames toNode:node];
901 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
902 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
903 if ((column != -1) && (draggedNodes != nil)) {
906 node = [browser itemAtRow:row inColumn:column];
908 node = [browser parentForItemsInColumn:column];
909 DLog(@"drag local node: %@", node.url);
910 if ([info draggingSourceOperationMask] & NSDragOperationMove)
911 return [self moveNodes:draggedNodes toNode:node];
912 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
913 return [self cpyNodes:draggedNodes toNode:node];
920 #pragma mark NSBrowser Actions
922 - (void)browserDoubleAction:(id)sender {
923 NSInteger column = [browser clickedColumn];
924 NSInteger row = [browser clickedRow];
925 if ((column == -1) || (row == -1))
927 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
928 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
929 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
930 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
931 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
932 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
935 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
937 NSMenuItem *menuItem = [[NSMenuItem alloc] init];
938 menuItem.representedObject = menuNodes;
939 [self menuDownload:menuItem];
943 #pragma mark Drag and Drop methods
945 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
946 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
947 if ([node class] == [PithosSubdirNode class]) {
948 // XXX newFilename and version are ignored in the case of a subdir node for now
949 // Operation: Download a subdir node and its descendants
950 // The resulting ASIPithosObjectRequests are chained through dependencies
951 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
953 if (operation.isCancelled)
955 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
956 containerName:node.pithosContainer.name
957 objectName:node.pithosObject.name
959 checkIfExists:checkIfExists
960 sharingAccount:node.sharingAccount];
961 if (!operation.isCancelled && objectRequests) {
962 ASIPithosObjectRequest *previousObjectRequest = nil;
963 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
964 if (operation.isCancelled)
966 objectRequest.delegate = self;
967 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
968 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
969 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
970 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
971 message:[messagePrefix stringByAppendingString:@" (0%)"]
972 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
974 dispatch_async(dispatch_get_main_queue(), ^{
975 [activityFacility updateActivity:activity withMessage:activity.message];
977 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
978 [NSDictionary dictionaryWithObjectsAndKeys:
979 activity, @"activity",
980 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
981 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
982 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
983 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
984 [NSNumber numberWithUnsignedInteger:10], @"retries",
985 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
986 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
987 downloadNetworkQueue, @"networkQueue",
988 @"download", @"operationType",
990 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
991 [activityFacility updateActivity:activity
992 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
993 totalBytes:activity.totalBytes
994 currentBytes:(activity.currentBytes + size)];
996 if (previousObjectRequest)
997 [objectRequest addDependency:previousObjectRequest];
998 previousObjectRequest = objectRequest;
999 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1004 [downloadQueue addOperation:operation];
1005 } else if ([node class] == [PithosObjectNode class]) {
1006 // Operation: Download an object node
1007 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1009 if (operation.isCancelled)
1011 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
1012 containerName:node.pithosContainer.name
1013 objectName:node.pithosObject.name
1016 withNewFileName:newFileName
1017 checkIfExists:checkIfExists
1018 sharingAccount:node.sharingAccount];
1019 if (!operation.isCancelled && objectRequest) {
1020 objectRequest.delegate = self;
1021 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1022 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1023 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1024 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1025 message:[messagePrefix stringByAppendingString:@" (0%)"]
1026 totalBytes:node.pithosObject.bytes
1028 dispatch_async(dispatch_get_main_queue(), ^{
1029 [activityFacility updateActivity:activity withMessage:activity.message];
1031 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1032 [NSDictionary dictionaryWithObjectsAndKeys:
1033 activity, @"activity",
1034 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1035 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1036 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1037 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1038 [NSNumber numberWithUnsignedInteger:10], @"retries",
1039 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1040 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1041 downloadNetworkQueue, @"networkQueue",
1042 @"download", @"operationType",
1044 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1045 [activityFacility updateActivity:activity
1046 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1047 totalBytes:activity.totalBytes
1048 currentBytes:(activity.currentBytes + size)];
1050 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1054 [downloadQueue addOperation:operation];
1058 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1059 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1061 NSFileManager *fileManager = [NSFileManager defaultManager];
1062 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1063 NSString *objectNamePrefix;
1064 if ([destinationNode class] == [PithosSubdirNode class])
1065 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1067 objectNamePrefix = [NSString string];
1068 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1069 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1070 containerName:containerName];
1071 [PithosUtilities startAndWaitForRequest:containerRequest];
1072 if ([containerRequest error]) {
1073 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1075 } else if (containerRequest.responseStatusCode != 204) {
1076 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1079 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1080 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1082 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1083 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1085 for (NSString *filePath in filenames) {
1087 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1090 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1091 precomposedStringWithCanonicalMapping];
1092 // Operation: Upload a local file
1093 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1095 if (operation.isCancelled)
1097 NSError *error = nil;
1098 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1099 if (contentType == nil)
1100 contentType = @"application/octet-stream";
1103 DLog(@"contentType detection error: %@", error);
1105 NSArray *hashes = nil;
1106 if (operation.isCancelled)
1108 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1109 containerName:containerName
1110 objectName:objectName
1111 contentType:contentType
1117 sharingAccount:destinationNode.sharingAccount];
1118 if (!operation.isCancelled && objectRequest) {
1119 objectRequest.delegate = self;
1120 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1121 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1122 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1123 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1124 message:[messagePrefix stringByAppendingString:@" (0%)"]
1125 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1127 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1128 [NSDictionary dictionaryWithObjectsAndKeys:
1129 containerName, @"containerName",
1130 objectName, @"objectName",
1131 contentType, @"contentType",
1132 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1133 blockHash, @"blockHash",
1134 filePath, @"filePath",
1136 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1137 [NSNumber numberWithBool:YES], @"refresh",
1138 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1139 activity, @"activity",
1140 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1141 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1142 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1143 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1144 [NSNumber numberWithUnsignedInteger:10], @"retries",
1145 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1146 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1147 uploadNetworkQueue, @"networkQueue",
1148 @"upload", @"operationType",
1150 if (destinationNode.sharingAccount)
1151 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1152 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1156 [uploadQueue addOperation:operation];
1158 // Upload directory, confirm first
1159 NSAlert *alert = [[NSAlert alloc] init];
1160 [alert setMessageText:@"Upload directory"];
1161 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1162 [alert addButtonWithTitle:@"OK"];
1163 [alert addButtonWithTitle:@"Cancel"];
1164 NSInteger choice = [alert runModal];
1165 if (choice == NSAlertFirstButtonReturn) {
1166 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1167 precomposedStringWithCanonicalMapping];
1168 // Operation: Upload a local directory and its descendants
1169 // The resulting ASIPithosObjectRequests are chained through dependencies
1170 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1172 if (operation.isCancelled)
1174 NSMutableArray *objectNames = nil;
1175 NSMutableArray *contentTypes = nil;
1176 NSMutableArray *filePaths = nil;
1177 NSMutableArray *hashesArrays = nil;
1178 NSMutableArray *directoryObjectRequests = nil;
1179 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1180 containerName:containerName
1181 objectName:objectName
1184 forDirectory:filePath
1186 objectNames:&objectNames
1187 contentTypes:&contentTypes
1188 filePaths:&filePaths
1189 hashesArrays:&hashesArrays
1190 directoryObjectRequests:&directoryObjectRequests
1191 sharingAccount:destinationNode.sharingAccount];
1192 if (operation.isCancelled)
1194 ASIPithosObjectRequest *previousObjectRequest = nil;
1195 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1196 if (operation.isCancelled)
1198 objectRequest.delegate = self;
1199 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1200 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1201 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1202 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1203 message:messagePrefix];
1204 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1205 [NSDictionary dictionaryWithObjectsAndKeys:
1206 [NSNumber numberWithBool:YES], @"refresh",
1207 activity, @"activity",
1208 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1209 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1210 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1211 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1212 [NSNumber numberWithUnsignedInteger:10], @"retries",
1213 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1214 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1215 uploadNetworkQueue, @"networkQueue",
1216 @"upload", @"operationType",
1218 if (previousObjectRequest)
1219 [objectRequest addDependency:previousObjectRequest];
1220 previousObjectRequest = objectRequest;
1221 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1223 if (!operation.isCancelled && objectRequests) {
1224 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1225 if (operation.isCancelled)
1227 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1228 objectRequest.delegate = self;
1229 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1230 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1231 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1232 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1233 message:[messagePrefix stringByAppendingString:@" (0%)"]
1234 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1236 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1237 [NSDictionary dictionaryWithObjectsAndKeys:
1238 containerName, @"containerName",
1239 [objectNames objectAtIndex:i], @"objectName",
1240 [contentTypes objectAtIndex:i], @"contentType",
1241 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1242 blockHash, @"blockHash",
1243 [filePaths objectAtIndex:i], @"filePath",
1244 [hashesArrays objectAtIndex:i], @"hashes",
1245 [NSNumber numberWithBool:YES], @"refresh",
1246 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1247 activity, @"activity",
1248 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1249 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1250 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1251 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1252 [NSNumber numberWithUnsignedInteger:10], @"retries",
1253 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1254 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1255 uploadNetworkQueue, @"networkQueue",
1256 @"upload", @"operationType",
1258 if (destinationNode.sharingAccount)
1259 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1260 if (previousObjectRequest)
1261 [objectRequest addDependency:previousObjectRequest];
1262 previousObjectRequest = objectRequest;
1263 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1268 [uploadQueue addOperation:operation];
1276 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1277 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1278 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1280 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1281 NSString *objectNamePrefix;
1282 if ([destinationNode class] == [PithosSubdirNode class])
1283 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1285 objectNamePrefix = [NSString string];
1287 for (PithosNode *node in nodes) {
1288 if (([node class] == [PithosObjectNode class]) ||
1289 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1290 // Operation: Move an object or subdir/ node
1291 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1293 if (operation.isCancelled)
1295 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1296 if ([node.pithosObject.name hasSuffix:@"/"])
1297 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1298 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1299 containerName:node.pithosContainer.name
1300 objectName:node.pithosObject.name
1301 destinationContainerName:containerName
1302 destinationObjectName:destinationObjectName
1304 if (!operation.isCancelled && objectRequest) {
1305 objectRequest.delegate = self;
1306 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1307 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1308 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1309 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1310 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1311 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1312 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1313 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1314 message:messagePrefix];
1315 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1316 [NSDictionary dictionaryWithObjectsAndKeys:
1317 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1318 activity, @"activity",
1319 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1320 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1321 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1322 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1323 [NSNumber numberWithUnsignedInteger:10], @"retries",
1324 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1325 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1326 moveNetworkQueue, @"networkQueue",
1327 @"move", @"operationType",
1329 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1333 [moveQueue addOperation:operation];
1334 } else if ([node class] == [PithosSubdirNode class]) {
1335 // Operation: Move a subdir node and its descendants
1336 // The resulting ASIPithosObjectRequests are chained through dependencies
1337 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1339 if (operation.isCancelled)
1341 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1342 if (node.pithosObject.subdir)
1343 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1344 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1345 containerName:node.pithosContainer.name
1346 objectName:node.pithosObject.name
1347 destinationContainerName:containerName
1348 destinationObjectName:destinationObjectName
1350 if (!operation.isCancelled && objectRequests) {
1351 ASIPithosObjectRequest *previousObjectRequest = nil;
1352 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1353 if (operation.isCancelled)
1355 objectRequest.delegate = self;
1356 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1357 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1358 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1359 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1360 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1361 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1362 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1363 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1364 message:messagePrefix];
1365 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1366 [NSDictionary dictionaryWithObjectsAndKeys:
1367 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1368 [NSNumber numberWithBool:YES], @"refresh",
1369 activity, @"activity",
1370 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1371 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1372 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1373 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1374 [NSNumber numberWithUnsignedInteger:10], @"retries",
1375 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1376 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1377 moveNetworkQueue, @"networkQueue",
1378 @"move", @"operationType",
1380 if (previousObjectRequest)
1381 [objectRequest addDependency:previousObjectRequest];
1382 previousObjectRequest = objectRequest;
1383 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1388 [moveQueue addOperation:operation];
1394 - (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1395 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1396 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1398 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1399 NSString *objectNamePrefix;
1400 if ([destinationNode class] == [PithosSubdirNode class])
1401 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1403 objectNamePrefix = [NSString string];
1405 for (PithosNode *node in nodes) {
1406 if (([node class] == [PithosObjectNode class]) ||
1407 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1408 // Operation: Copy an object or subdir/ node
1409 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1411 if (operation.isCancelled)
1413 NSString *destinationObjectName;
1414 if (![destinationNode isEqualTo:node.parent]) {
1415 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1416 if ([node.pithosObject.name hasSuffix:@"/"])
1417 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1419 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1420 containerName:containerName
1421 objectName:node.pithosObject.name];
1423 if (operation.isCancelled)
1425 ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithos
1426 containerName:node.pithosContainer.name
1427 objectName:node.pithosObject.name
1428 destinationContainerName:containerName
1429 destinationObjectName:destinationObjectName
1431 sharingAccount:node.sharingAccount];
1432 if (!operation.isCancelled && objectRequest) {
1433 objectRequest.delegate = self;
1434 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1435 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1436 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1437 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1438 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1439 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1440 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1441 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1442 message:messagePrefix];
1443 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1444 [NSDictionary dictionaryWithObjectsAndKeys:
1445 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1446 activity, @"activity",
1447 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1448 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1449 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1450 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1451 [NSNumber numberWithUnsignedInteger:10], @"retries",
1452 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1453 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1454 copyNetworkQueue, @"networkQueue",
1455 @"copy", @"operationType",
1457 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1461 [copyQueue addOperation:operation];
1462 } else if ([node class] == [PithosSubdirNode class]) {
1463 // Operation: Copy a subdir node and its descendants
1464 // The resulting ASIPithosObjectRequests are chained through dependencies
1465 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1467 if (operation.isCancelled)
1469 NSString *destinationObjectName;
1470 if (![destinationNode isEqualTo:node.parent]) {
1471 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1472 if (node.pithosObject.subdir)
1473 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1475 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1476 containerName:containerName
1477 subdirName:node.pithosObject.name];
1479 if (operation.isCancelled)
1481 NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithos
1482 containerName:node.pithosContainer.name
1483 objectName:node.pithosObject.name
1484 destinationContainerName:containerName
1485 destinationObjectName:destinationObjectName
1487 sharingAccount:node.sharingAccount];
1488 if (!operation.isCancelled && objectRequests) {
1489 ASIPithosObjectRequest *previousObjectRequest = nil;
1490 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1491 if (operation.isCancelled)
1493 objectRequest.delegate = self;
1494 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1495 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1496 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1497 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1498 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1499 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1500 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1501 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1502 message:messagePrefix];
1503 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1504 [NSDictionary dictionaryWithObjectsAndKeys:
1505 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1506 activity, @"activity",
1507 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1508 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1509 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1510 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1511 [NSNumber numberWithUnsignedInteger:10], @"retries",
1512 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1513 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1514 copyNetworkQueue, @"networkQueue",
1515 @"copy", @"operationType",
1517 if (previousObjectRequest)
1518 [objectRequest addDependency:previousObjectRequest];
1519 previousObjectRequest = objectRequest;
1520 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1525 [copyQueue addOperation:operation];
1532 #pragma mark ASIHTTPRequestDelegate
1534 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1535 NSOperationQueue *callbackQueue;
1536 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1537 if ([operationType isEqualToString:@"move"])
1538 callbackQueue = moveCallbackQueue;
1539 else if ([operationType isEqualToString:@"copy"])
1540 callbackQueue = copyCallbackQueue;
1541 else if ([operationType isEqualToString:@"delete"])
1542 callbackQueue = deleteCallbackQueue;
1543 else if ([operationType isEqualToString:@"upload"])
1544 callbackQueue = uploadCallbackQueue;
1545 else if ([operationType isEqualToString:@"download"])
1546 callbackQueue = downloadCallbackQueue;
1548 dispatch_async(dispatch_get_main_queue(), ^{
1549 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1550 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1554 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1555 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1556 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1558 operation.completionBlock = ^{
1560 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1561 dispatch_async(dispatch_get_main_queue(), ^{
1562 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1563 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1568 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1569 [callbackQueue addOperation:operation];
1572 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1573 if (request.isCancelled) {
1574 // Request has been cancelled
1575 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1576 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1577 withObject:request];
1579 NSOperationQueue *callbackQueue;
1580 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1581 if ([operationType isEqualToString:@"move"])
1582 callbackQueue = moveCallbackQueue;
1583 else if ([operationType isEqualToString:@"copy"])
1584 callbackQueue = copyCallbackQueue;
1585 else if ([operationType isEqualToString:@"delete"])
1586 callbackQueue = deleteCallbackQueue;
1587 else if ([operationType isEqualToString:@"upload"])
1588 callbackQueue = uploadCallbackQueue;
1589 else if ([operationType isEqualToString:@"download"])
1590 callbackQueue = downloadCallbackQueue;
1592 dispatch_async(dispatch_get_main_queue(), ^{
1593 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1594 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1598 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1599 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1600 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1602 operation.completionBlock = ^{
1604 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1605 dispatch_async(dispatch_get_main_queue(), ^{
1606 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1607 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1612 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1613 [callbackQueue addOperation:operation];
1617 - (void)requestFailed:(ASIPithosRequest *)request {
1619 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1620 DLog(@"Request failed: %@", request.url);
1621 if (operation.isCancelled)
1623 if (request.isCancelled) {
1624 dispatch_async(dispatch_get_main_queue(), ^{
1625 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1626 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1630 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1632 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities copyRequest:request];
1633 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1634 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1635 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1637 dispatch_async(dispatch_get_main_queue(), ^{
1638 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1639 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1641 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1642 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1644 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1649 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1651 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1652 DLog(@"Download finished: %@", objectRequest.url);
1653 if (operation.isCancelled) {
1654 [self requestFailed:objectRequest];
1655 } else if (objectRequest.responseStatusCode == 200) {
1656 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1657 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1658 NSUInteger totalBytes = activity.totalBytes;
1660 // XXX change contentLength to objectContentLength if it is fixed in the server
1661 if ([objectRequest contentLength] == 0) {
1662 // The check above was:
1663 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1664 // I checked for directory content types in order not to create a file in place of a directory,
1665 // but this callback method is not called in the case of a directory download.
1666 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1667 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1668 DLog(@"Downloaded 0 bytes");
1669 NSFileManager *fileManager = [NSFileManager defaultManager];
1670 if (![fileManager fileExistsAtPath:filePath]) {
1671 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1672 dispatch_async(dispatch_get_main_queue(), ^{
1673 NSAlert *alert = [[NSAlert alloc] init];
1674 [alert setMessageText:@"Create File Error"];
1675 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1676 [alert addButtonWithTitle:@"OK"];
1683 NSUInteger currentBytes = [objectRequest objectContentLength];
1684 if (currentBytes == 0)
1685 currentBytes = totalBytes;
1686 dispatch_async(dispatch_get_main_queue(), ^{
1687 [activityFacility endActivity:activity
1688 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1689 totalBytes:totalBytes
1690 currentBytes:currentBytes];
1693 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1694 [self requestFailed:objectRequest];
1699 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1701 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1702 DLog(@"Upload directory object finished: %@", objectRequest.url);
1703 if (operation.isCancelled) {
1704 [self requestFailed:objectRequest];
1705 } else if (objectRequest.responseStatusCode == 201) {
1706 DLog(@"Directory object created: %@", objectRequest.url);
1707 dispatch_async(dispatch_get_main_queue(), ^{
1708 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1709 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1710 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1711 [node forceRefresh];
1713 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1716 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1717 [self forceRefresh:self];
1718 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1719 [self refresh:self];
1722 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1723 [self requestFailed:objectRequest];
1728 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1730 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1731 DLog(@"Upload using hashmap finished: %@", objectRequest.url);
1732 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1733 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1734 NSUInteger totalBytes = activity.totalBytes;
1735 NSUInteger currentBytes = activity.currentBytes;
1736 if (operation.isCancelled) {
1737 [self requestFailed:objectRequest];
1738 } else if (objectRequest.responseStatusCode == 201) {
1739 DLog(@"Object created: %@", objectRequest.url);
1740 dispatch_async(dispatch_get_main_queue(), ^{
1741 [activityFacility endActivity:activity
1742 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1743 totalBytes:totalBytes
1744 currentBytes:totalBytes];
1745 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1746 [node forceRefresh];
1748 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1751 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1752 [self forceRefresh:self];
1753 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1754 [self refresh:self];
1756 } else if (objectRequest.responseStatusCode == 409) {
1757 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1758 if (iteration == 0) {
1759 DLog(@"Upload iteration limit reached: %@", objectRequest.url);
1760 dispatch_async(dispatch_get_main_queue(), ^{
1761 [activityFacility endActivity:activity
1762 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1763 NSAlert *alert = [[NSAlert alloc] init];
1764 [alert setMessageText:@"Upload Timeout"];
1765 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1766 [objectRequest.userInfo objectForKey:@"objectName"]]];
1767 [alert addButtonWithTitle:@"OK"];
1772 DLog(@"object is missing hashes: %@", objectRequest.url);
1773 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1774 withMissingHashes:[objectRequest hashes]];
1775 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1776 if (totalBytes >= [missingBlocks count]*blockSize)
1777 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1778 dispatch_async(dispatch_get_main_queue(), ^{
1779 [activityFacility updateActivity:activity
1780 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1781 totalBytes:totalBytes
1782 currentBytes:currentBytes];
1784 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1785 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1786 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1788 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1789 missingBlockIndex:missingBlockIndex
1790 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1791 newContainerRequest.delegate = self;
1792 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1793 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1794 newContainerRequest.userInfo = objectRequest.userInfo;
1795 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1796 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1797 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1798 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1799 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1800 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1801 [activityFacility updateActivity:activity
1802 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1803 totalBytes:activity.totalBytes
1804 currentBytes:(activity.currentBytes + size)];
1806 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1808 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1809 [self requestFailed:objectRequest];
1814 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1816 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1817 DLog(@"Upload of missing block finished: %@", containerRequest.url);
1818 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1819 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1820 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1821 if (operation.isCancelled) {
1822 [self requestFailed:containerRequest];
1823 } else if (containerRequest.responseStatusCode == 202) {
1824 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1825 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1826 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1827 if (missingBlockIndex == NSNotFound) {
1828 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1829 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1830 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1831 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1832 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1834 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1835 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1838 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1839 newObjectRequest.delegate = self;
1840 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1841 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1842 newObjectRequest.userInfo = containerRequest.userInfo;
1843 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1844 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1845 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1846 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1847 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1849 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1850 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1851 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1852 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1853 missingBlockIndex:missingBlockIndex
1854 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1855 newContainerRequest.delegate = self;
1856 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1857 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1858 newContainerRequest.userInfo = containerRequest.userInfo;
1859 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1860 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1861 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1862 [activityFacility updateActivity:activity
1863 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1864 totalBytes:activity.totalBytes
1865 currentBytes:(activity.currentBytes + size)];
1867 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1870 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1871 [self requestFailed:containerRequest];
1876 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1878 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1879 DLog(@"Move object finished: %@", objectRequest.url);
1880 if (operation.isCancelled) {
1881 [self requestFailed:objectRequest];
1882 } else if (objectRequest.responseStatusCode == 201) {
1883 dispatch_async(dispatch_get_main_queue(), ^{
1884 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1885 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1886 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1887 [node forceRefresh];
1889 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1892 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1893 [self forceRefresh:self];
1894 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1895 [self refresh:self];
1898 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1899 [self requestFailed:objectRequest];
1904 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1906 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1907 DLog(@"Copy object finished: %@", objectRequest.url);
1908 if (operation.isCancelled) {
1909 [self requestFailed:objectRequest];
1910 } else if (objectRequest.responseStatusCode == 201) {
1911 dispatch_async(dispatch_get_main_queue(), ^{
1912 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1913 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1914 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1915 [node forceRefresh];
1917 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1920 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1921 [self forceRefresh:self];
1922 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1923 [self refresh:self];
1926 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1927 [self requestFailed:objectRequest];
1932 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1934 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1935 DLog(@"Delete object finished: %@", objectRequest.url);
1936 if (operation.isCancelled) {
1937 [self requestFailed:objectRequest];
1938 } else if (objectRequest.responseStatusCode == 204) {
1939 dispatch_async(dispatch_get_main_queue(), ^{
1940 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1941 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1942 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1943 [node forceRefresh];
1945 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1948 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1949 [self forceRefresh:self];
1950 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1951 [self refresh:self];
1954 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1955 [self requestFailed:objectRequest];
1961 #pragma mark NSSplitViewDelegate
1963 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1964 if (splitView == verticalSplitView)
1967 return ([horizontalSplitView bounds].size.height - 142);
1970 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1971 if (splitView == verticalSplitView)
1974 return ([horizontalSplitView bounds].size.height - 108);
1977 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view {
1978 if (((splitView == verticalSplitView) && (view == leftView)) ||
1979 ((splitView == horizontalSplitView) && (view == leftBottomView))) {
1986 #pragma mark NSOutlineViewDataSource
1988 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1989 if (!browserInitialized)
1993 if (item == containersNode)
1994 return containersNodeChildren.count;
1995 if (item == sharedNode)
2000 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2001 if (!browserInitialized)
2004 return (!index ? containersNode : sharedNode);
2005 if (item == sharedNode)
2006 return (!index ? mySharedNode : othersSharedNode);
2007 return [containersNodeChildren objectAtIndex:index];
2010 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2011 if ((item == containersNode) || (item == sharedNode))
2016 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2017 PithosNode *node = (PithosNode *)item;
2021 #pragma mark Drag and Drop destination
2023 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2024 validateDrop:(id<NSDraggingInfo>)info
2025 proposedItem:(id)item
2026 proposedChildIndex:(NSInteger)index {
2027 NSDragOperation result = NSDragOperationNone;
2028 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2030 PithosNode *dropNode = (PithosNode *)item;
2031 if ([dropNode class] != [PithosContainerNode class])
2033 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2034 result = NSDragOperationCopy;
2035 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2036 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2037 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2038 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2039 if (![dropNode isEqualTo:draggedParentNode])
2040 result = NSDragOperationMove;
2041 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2042 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2043 result = NSDragOperationCopy;
2049 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2050 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2051 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2052 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2053 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2054 PithosNode *node = (PithosNode *)item;
2055 DLog(@"drag in node: %@", node.url);
2056 return [self uploadFiles:filenames toNode:node];
2058 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2059 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2060 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2061 PithosNode *node = (PithosNode *)item;
2062 DLog(@"drag local node: %@", node.url);
2063 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2064 ([info draggingSourceOperationMask] & NSDragOperationMove))
2065 return [self moveNodes:draggedNodes toNode:node];
2066 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2067 return [self cpyNodes:draggedNodes toNode:node];
2074 #pragma mark NSOutlineViewDelegate
2076 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2077 if ((item == containersNode) || (item == sharedNode))
2082 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2083 if ((item == containersNode) || (item == sharedNode))
2088 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2089 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2092 [browser loadColumnZero];
2098 #pragma mark NSMenuDelegate
2100 - (void)menuNeedsUpdate:(NSMenu *)menu {
2101 [menu removeAllItems];
2102 NSMenuItem *menuItem;
2103 NSString *menuItemTitle;
2104 BOOL nodeContextMenu = NO;
2105 PithosNode *menuNode = nil;
2106 NSMutableArray *menuNodes;
2107 if (menu == browserMenu) {
2108 NSInteger column = [browser clickedColumn];
2109 NSInteger row = [browser clickedRow];
2110 if ((column == -1) || (row == -1)) {
2112 // General context menu
2113 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2114 if ([menuNodesIndexPaths count] == 0) {
2115 menuNode = [browser parentForItemsInColumn:0];
2116 } else if (([menuNodesIndexPaths count] != 1) ||
2117 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2118 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2120 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2123 menuNode = [browser parentForItemsInColumn:column];
2124 if ([menuNode class] == [PithosObjectNode class]) {
2125 // Node context menu
2126 menuNodes = [NSMutableArray arrayWithObject:menuNode];
2127 nodeContextMenu = YES;
2130 // General context menu
2133 // Node context menu
2134 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2135 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2136 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2137 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2138 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2139 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2142 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2144 nodeContextMenu = YES;
2146 } else if (menu == outlineViewMenu) {
2147 NSInteger row = [outlineView clickedRow];
2149 row = [outlineView selectedRow];
2152 menuNode = [outlineView itemAtRow:row];
2155 if (!nodeContextMenu) {
2156 // General context menu
2157 if (([menuNode class] == [PithosAccountNode class]) ||
2158 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2159 ([menuNode class] == [PithosEmptyNode class]))
2162 if (!menuNode.shared && !menuNode.sharingAccount) {
2163 menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""];
2164 [menuItem setRepresentedObject:menuNode];
2165 [menu addItem:menuItem];
2166 [menu addItem:[NSMenuItem separatorItem]];
2169 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2170 [menu addItem:menuItem];
2171 [menu addItem:[NSMenuItem separatorItem]];
2173 menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2174 action:@selector(menuGetInfo:)
2176 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2177 [menu addItem:menuItem];
2179 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2180 (([menuNode class] == [PithosContainerNode class]) ||
2181 (([menuNode class] == [PithosSubdirNode class]) &&
2182 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2183 NSUInteger clipboardNodesCount = [clipboardNodes count];
2184 if (clipboardNodesCount == 0) {
2185 self.clipboardNodes = nil;
2186 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2187 if (clipboardNodesCount == 1)
2188 menuItemTitle = @"Paste Item";
2190 menuItemTitle = @"Paste Items";
2191 [menu addItem:[NSMenuItem separatorItem]];
2192 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2193 [menuItem setRepresentedObject:menuNode];
2194 [menu addItem:menuItem];
2198 // Node context menu
2199 NSUInteger menuNodesCount = [menuNodes count];
2200 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2202 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2203 menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""];
2204 [menuItem setRepresentedObject:menuNodes];
2205 [menu addItem:menuItem];
2206 [menu addItem:[NSMenuItem separatorItem]];
2208 // Move to Trash (pithos container only)
2210 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2211 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2212 menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2213 action:@selector(menuMoveToTrash:)
2215 [menuItem setRepresentedObject:menuNodes];
2216 [menu addItem:menuItem];
2218 menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""];
2219 [menuItem setRepresentedObject:menuNodes];
2220 [menu addItem:menuItem];
2221 [menu addItem:[NSMenuItem separatorItem]];
2224 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2225 [menu addItem:menuItem];
2227 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2228 [menu addItem:[NSMenuItem separatorItem]];
2229 menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2230 action:@selector(menuGetInfo:)
2232 [menuItem setRepresentedObject:menuNodes];
2233 [menu addItem:menuItem];
2235 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2236 [menu addItem:[NSMenuItem separatorItem]];
2239 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2240 if (menuNodesCount == 1)
2241 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2243 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2244 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""];
2245 [menuItem setRepresentedObject:menuNodes];
2246 [menu addItem:menuItem];
2249 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2250 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2251 if (menuNodesCount == 1)
2252 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2254 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2255 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""];
2256 [menuItem setRepresentedObject:menuNodes];
2257 [menu addItem:menuItem];
2260 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2261 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2262 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2263 NSUInteger clipboardNodesCount = [clipboardNodes count];
2264 if (clipboardNodesCount == 0) {
2265 self.clipboardNodes = nil;
2266 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2267 if (clipboardNodesCount == 1)
2268 menuItemTitle = @"Paste Item";
2270 menuItemTitle = @"Paste Items";
2271 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2272 [menuItem setRepresentedObject:firstMenuNode];
2273 [menu addItem:menuItem];
2280 #pragma mark NSMenuValidation
2282 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2283 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2284 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2285 if ([menuNodesIndexPaths count] == 0)
2288 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2289 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2290 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2291 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2292 ((menuItem.action == @selector(delete:)) &&
2293 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2294 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2297 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2298 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2299 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2301 menuItem.representedObject = menuNodes;
2302 } else if (menuItem.action == @selector(paste:)) {
2303 if (!clipboardNodes || ![clipboardNodes count])
2306 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2307 PithosNode *menuNode;
2308 if ([menuNodesIndexPaths count] == 0)
2309 menuNode = [browser parentForItemsInColumn:0];
2310 else if (([menuNodesIndexPaths count] != 1) ||
2311 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2312 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2314 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2316 if (menuNode.shared || menuNode.sharingAccount ||
2317 (([menuNode class] != [PithosContainerNode class]) &&
2318 (([menuNode class] != [PithosSubdirNode class]) ||
2319 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2320 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2323 menuItem.representedObject = menuNode;
2328 - (void)cut:(NSMenuItem *)sender {
2329 [self menuCut:sender];
2332 - (void)copy:(NSMenuItem *)sender {
2333 [self menuCopy:sender];
2336 - (void)paste:(NSMenuItem *)sender {
2337 [self menuPaste:sender];
2340 - (void)delete:(NSMenuItem *)sender {
2341 if (sender.tag == 0)
2342 [self menuMoveToTrash:sender];
2344 [self menuDelete:sender];
2348 #pragma mark Menu Actions
2350 - (void)menuNewFolder:(NSMenuItem *)sender {
2351 PithosNode *node = (PithosNode *)[sender representedObject];
2352 if ([node class] == [PithosContainerNode class]) {
2353 // Operation: Create (upload) a new root application/directory object
2354 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2356 if (operation.isCancelled)
2358 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2359 containerName:node.pithosContainer.name
2360 subdirName:@"untitled folder"];
2361 NSString *fileName = [safeObjectName lastPathComponent];
2362 if (operation.isCancelled)
2364 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2365 containerName:node.pithosContainer.name
2366 objectName:safeObjectName
2368 contentType:@"application/directory"
2370 contentDisposition:nil
2373 isPublic:ASIPithosObjectRequestPublicIgnore
2375 data:[NSData data]];
2376 objectRequest.delegate = self;
2377 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2378 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2379 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2380 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2381 message:messagePrefix];
2382 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2383 fileName, @"fileName",
2384 [NSArray arrayWithObject:node], @"refreshNodes",
2385 [NSNumber numberWithBool:YES], @"refresh",
2386 activity, @"activity",
2387 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2388 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2389 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2390 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2391 [NSNumber numberWithUnsignedInteger:10], @"retries",
2392 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2393 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2394 uploadNetworkQueue, @"networkQueue",
2395 @"upload", @"operationType",
2397 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2400 [uploadQueue addOperation:operation];
2401 } else if (([node class] == [PithosSubdirNode class]) &&
2402 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2403 // Operation: Create (upload) a new aplication/directory object
2404 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2406 if (operation.isCancelled)
2408 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2409 containerName:node.pithosContainer.name
2410 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2411 NSString *fileName = [safeObjectName lastPathComponent];
2412 if (operation.isCancelled)
2414 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2415 containerName:node.pithosContainer.name
2416 objectName:safeObjectName
2418 contentType:@"application/directory"
2420 contentDisposition:nil
2423 isPublic:ASIPithosObjectRequestPublicIgnore
2425 data:[NSData data]];
2426 objectRequest.delegate = self;
2427 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2428 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2429 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2430 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2431 message:messagePrefix];
2432 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2433 fileName, @"fileName",
2434 [NSArray arrayWithObject:node], @"refreshNodes",
2435 [NSNumber numberWithBool:YES], @"refresh",
2436 activity, @"activity",
2437 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2438 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2439 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2440 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2441 [NSNumber numberWithUnsignedInteger:10], @"retries",
2442 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2443 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2444 uploadNetworkQueue, @"networkQueue",
2445 @"upload", @"operationType",
2447 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2450 [uploadQueue addOperation:operation];
2454 - (void)menuGetInfo:(NSMenuItem *)sender {
2455 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2456 [node showPithosNodeInfo:sender];
2460 - (void)menuDownload:(NSMenuItem *)sender {
2461 NSArray *nodes = (NSArray *)[sender representedObject];
2462 PithosNode *firstNode = [nodes objectAtIndex:0];
2463 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2464 NSSavePanel *save = [NSSavePanel savePanel];
2465 save.nameFieldStringValue = firstNode.displayName;
2466 NSInteger result = [save runModal];
2467 if (result == NSOKButton) {
2468 NSString *destinationPath = save.URL.path;
2469 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2470 NSString *newFileName = [destinationPath lastPathComponent];
2471 if ([destinationPath hasSuffix:@"/"])
2472 newFileName = [newFileName stringByAppendingString:@"/"];
2473 if ([firstNode.displayName isEqualToString:newFileName])
2475 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2478 NSOpenPanel *open = [NSOpenPanel openPanel];
2479 open.canChooseFiles = NO;
2480 open.canChooseDirectories = YES;
2481 open.canCreateDirectories = YES;
2482 NSInteger result = [open runModal];
2483 if (result == NSOKButton) {
2484 NSString *directoryPath = open.URL.path;
2485 for (PithosNode *node in nodes) {
2486 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2492 - (void)menuDelete:(NSMenuItem *)sender {
2493 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2494 if (([node class] == [PithosObjectNode class]) ||
2495 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2496 // Operation: Delete an object or subdir/ node
2497 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2499 if (operation.isCancelled)
2501 NSString *fileName = [node.pithosObject.name lastPathComponent];
2502 if ([node.pithosObject.name hasSuffix:@"/"])
2503 fileName = [fileName stringByAppendingString:@"/"];
2504 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2505 containerName:node.pithosContainer.name
2506 objectName:node.pithosObject.name];
2507 if (operation.isCancelled)
2509 objectRequest.delegate = self;
2510 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2511 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2512 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2513 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2514 message:messagePrefix];
2515 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2516 fileName, @"fileName",
2517 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2518 activity, @"activity",
2519 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2520 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2521 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2522 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2523 [NSNumber numberWithUnsignedInteger:10], @"retries",
2524 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2525 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2526 deleteNetworkQueue, @"networkQueue",
2527 @"delete", @"operationType",
2529 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2532 [deleteQueue addOperation:operation];
2533 } else if ([node class] == [PithosSubdirNode class]) {
2534 // Operation: Delete a subdir node and its descendants
2535 // The resulting ASIPithosObjectRequests are chained through dependencies
2536 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2538 if (operation.isCancelled)
2540 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2541 containerName:node.pithosContainer.name
2542 objectName:node.pithosObject.name];
2543 if (!operation.isCancelled && objectRequests) {
2544 ASIPithosObjectRequest *previousObjectRequest = nil;
2545 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2546 if (operation.isCancelled)
2548 objectRequest.delegate = self;
2549 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2550 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2551 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2552 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2553 message:messagePrefix];
2554 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2555 [NSDictionary dictionaryWithObjectsAndKeys:
2556 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2557 activity, @"activity",
2558 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2559 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2560 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2561 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2562 [NSNumber numberWithUnsignedInteger:10], @"retries",
2563 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2564 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2565 deleteNetworkQueue, @"networkQueue",
2566 @"delete", @"operationType",
2568 if (previousObjectRequest)
2569 [objectRequest addDependency:previousObjectRequest];
2570 previousObjectRequest = objectRequest;
2571 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2576 [deleteQueue addOperation:operation];
2581 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2582 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2583 if (([node class] == [PithosObjectNode class]) ||
2584 (([node class] == [PithosSubdirNode class]) &&
2585 !node.pithosObject.subdir &&
2586 [node.pithosObject.name hasSuffix:@"/"])) {
2587 // Operation: Move to trash an object or subdir/ node
2588 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2590 if (operation.isCancelled)
2592 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2593 containerName:@"trash"
2594 objectName:node.pithosObject.name];
2595 if (!operation.isCancelled && safeObjectName) {
2596 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2597 containerName:node.pithosContainer.name
2598 objectName:node.pithosObject.name
2599 destinationContainerName:@"trash"
2600 destinationObjectName:safeObjectName
2602 if (!operation.isCancelled && objectRequest) {
2603 objectRequest.delegate = self;
2604 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2605 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2606 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2607 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2608 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2609 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2610 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2611 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2612 message:messagePrefix];
2613 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2614 [NSDictionary dictionaryWithObjectsAndKeys:
2615 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2616 activity, @"activity",
2617 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2618 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2619 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2620 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2621 [NSNumber numberWithUnsignedInteger:10], @"retries",
2622 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2623 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2624 moveNetworkQueue, @"networkQueue",
2625 @"move", @"operationType",
2627 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2632 [moveQueue addOperation:operation];
2633 } else if ([node class] == [PithosSubdirNode class]) {
2634 // Operation: Move to trash a subdir node and its descendants
2635 // The resulting ASIPithosObjectRequests are chained through dependencies
2636 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2638 if (operation.isCancelled)
2640 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2641 containerName:@"trash"
2642 subdirName:node.pithosObject.name];
2643 if (!operation.isCancelled && safeObjectName) {
2644 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2645 containerName:node.pithosContainer.name
2646 objectName:node.pithosObject.name
2647 destinationContainerName:@"trash"
2648 destinationObjectName:safeObjectName
2650 if (!operation.isCancelled && objectRequests) {
2651 ASIPithosObjectRequest *previousObjectRequest = nil;
2652 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2653 if (operation.isCancelled)
2655 objectRequest.delegate = self;
2656 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2657 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2658 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2659 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2660 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2661 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2662 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2663 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2664 message:messagePrefix];
2665 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2666 [NSDictionary dictionaryWithObjectsAndKeys:
2667 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2668 activity, @"activity",
2669 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2670 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2671 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2672 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2673 [NSNumber numberWithUnsignedInteger:10], @"retries",
2674 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2675 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2676 moveNetworkQueue, @"networkQueue",
2677 @"move", @"operationType",
2679 if (previousObjectRequest)
2680 [objectRequest addDependency:previousObjectRequest];
2681 previousObjectRequest = objectRequest;
2682 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2688 [moveQueue addOperation:operation];
2693 - (void)menuCut:(NSMenuItem *)sender {
2694 self.clipboardNodes = (NSArray *)[sender representedObject];
2695 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2696 self.clipboardCopy = NO;
2699 - (void)menuCopy:(NSMenuItem *)sender {
2700 self.clipboardNodes = (NSArray *)[sender representedObject];
2701 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2702 self.clipboardCopy = YES;
2705 - (void)menuPaste:(NSMenuItem *)sender {
2706 if (!clipboardNodes || ![clipboardNodes count])
2708 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2709 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2710 if (clipboardCopy) {
2711 [self cpyNodes:localClipboardNodes toNode:dropNode];
2712 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2713 self.clipboardNodes = nil;
2714 self.clipboardParentNode = nil;
2715 [self moveNodes:localClipboardNodes toNode:dropNode];
2720 #pragma mark PithosActivityFacilityDelegate
2722 - (void)activityUpdate:(NSDictionary *)info {
2723 NSString *message = [info objectForKey:@"message"];
2724 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2725 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2726 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2727 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2728 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2729 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2730 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2731 if (runningActivitiesCount && totalBytes) {
2732 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2733 [activityProgressIndicator startAnimation:self];
2735 [activityProgressIndicator setDoubleValue:1.0];
2736 [activityProgressIndicator stopAnimation:self];
2740 message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
2741 [activityTextField setStringValue:message];