2 // PithosBrowserController.m
5 // Copyright 2011-2013 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 "PithosAccount.h"
58 #import "UsingSizeTransformer.h"
60 #define REFRESH_TIMER_INTERVAL 5
62 @interface PithosBrowserCell : FileSystemBrowserCell {}
65 @implementation PithosBrowserCell
68 if ((self = [super init])) {
69 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
70 [self setEditable:YES];
75 - (void)setObjectValue:(id)object {
76 if ([object isKindOfClass:[PithosNode class]]) {
77 PithosNode *node = (PithosNode *)object;
78 [self setStringValue:node.displayName];
79 [self setImage:node.icon];
81 [super setObjectValue:object];
87 @interface PithosOutlineViewCell : ImageAndTextCell {}
90 @implementation PithosOutlineViewCell
92 - (void)setObjectValue:(id)object {
93 if ([object isKindOfClass:[PithosNode class]]) {
94 PithosNode *node = (PithosNode *)object;
95 [self setStringValue:node.displayName];
96 [self setImage:node.icon];
97 [self setEditable:NO];
99 [super setObjectValue:object];
105 @interface PithosBrowserController (Private)
106 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
107 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
108 - (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
111 @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];
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];
217 mySharedNode.displayName = @"shared by me";
218 mySharedNode.shared = YES;
219 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
220 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithosAccountManager:pithosAccountManager];
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.pithosAccountManager = pithosAccountManager;
342 mySharedNode.pithosAccountManager = pithosAccountManager;
343 [mySharedNode reset];
344 othersSharedNode.pithosAccountManager = pithosAccountManager;
345 [othersSharedNode reset];
347 // [activityFacility reset];
348 activityFacility.delegate = self;
350 refreshTimer = [NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
352 selector:@selector(forceRefresh:)
355 @synchronized(self) {
360 - (BOOL)operationsPending {
361 return ([moveNetworkQueue operationCount] ||
362 [copyNetworkQueue operationCount] ||
363 [deleteNetworkQueue operationCount] ||
364 [uploadNetworkQueue operationCount] ||
365 [downloadNetworkQueue operationCount] ||
366 [moveQueue operationCount] ||
367 [copyQueue operationCount] ||
368 [deleteQueue operationCount] ||
369 [uploadQueue operationCount] ||
370 [downloadQueue operationCount] ||
371 [moveCallbackQueue operationCount] ||
372 [copyCallbackQueue operationCount] ||
373 [deleteCallbackQueue operationCount] ||
374 [uploadCallbackQueue operationCount] ||
375 [downloadCallbackQueue operationCount]);
379 [[NSNotificationCenter defaultCenter] removeObserver:self];
383 - (void)setPithosAccountManager:(PithosAccount *)aPithosAccountManager {
384 if (aPithosAccountManager && (aPithosAccountManager != pithosAccountManager)) {
386 if (pithosAccountManager)
387 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"PithosAccountPithosChanged" object:pithosAccountManager];
388 pithosAccountManager = aPithosAccountManager;
389 [[NSNotificationCenter defaultCenter] addObserver:self
390 selector:@selector(pithosAccountManagerPithosChanged:)
391 name:@"PithosAccountPithosChanged"
392 object:pithosAccountManager];
398 #pragma mark Observers
400 -(void)pithosAccountManagerPithosChanged:(NSNotification *)notification {
401 if (![NSThread isMainThread]) {
402 [self performSelectorOnMainThread:@selector(pithosAccountManagerPithosChanged:) withObject:notification waitUntilDone:NO];
409 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
410 if (![NSThread isMainThread]) {
411 [self performSelectorOnMainThread:@selector(pithosNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
414 PithosNode *node = (PithosNode *)[notification object];
415 if ((node == accountNode) || (node.pithosAccountManager != pithosAccountManager))
417 DLog(@"pithosNodeChildrenUpdated:%@", node.url);
418 NSInteger lastColumn = [browser lastColumn];
419 for (NSInteger column = lastColumn; column >= 0; column--) {
420 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
421 [browser reloadColumn:column];
427 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
428 if (![NSThread isMainThread]) {
429 [self performSelectorOnMainThread:@selector(pithosAccountNodeChildrenUpdated:) withObject:notification waitUntilDone:NO];
432 BOOL containerPithosFound = NO;
433 BOOL containerTrashFound = NO;
434 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
435 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
436 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
437 [removedContainersNodeChildren addIndex:i];
439 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
440 for (PithosContainerNode *containerNode in accountNode.children) {
441 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
442 if (![containersNodeChildren containsObject:containerNode])
443 [containersNodeChildren insertObject:containerNode atIndex:0];
444 containerPithosFound = YES;
445 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
446 NSUInteger insertIndex = 1;
447 if (!containerPithosFound)
449 if (![containersNodeChildren containsObject:containerNode])
450 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
451 containerTrashFound = YES;
452 } else if (![containersNodeChildren containsObject:containerNode]) {
453 [containersNodeChildren addObject:containerNode];
456 BOOL refreshAccountNode = NO;
457 if (!containerPithosFound) {
458 // Create pithos node
459 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithosAccountManager.pithos
460 containerName:@"pithos"];
461 [PithosUtilities startAndWaitForRequest:containerRequest];
462 if ([containerRequest error]) {
463 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
465 refreshAccountNode = YES;
468 if (!containerTrashFound) {
470 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithosAccountManager.pithos
471 containerName:@"trash"];
472 [PithosUtilities startAndWaitForRequest:containerRequest];
473 if ([containerRequest error]) {
474 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
476 refreshAccountNode = YES;
480 if (refreshAccountNode)
481 [accountNode refresh];
483 [outlineView reloadData];
485 // Expand the folder outline view
486 [outlineView expandItem:nil expandChildren:YES];
488 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
489 rootNode = [containersNodeChildren objectAtIndex:0];
490 [browser loadColumnZero];
497 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
504 - (IBAction)forceRefresh:(id)sender {
505 if (![NSThread isMainThread]) {
506 [self performSelectorOnMainThread:@selector(forceRefresh:) withObject:sender waitUntilDone:NO];
512 [accountNode forceRefresh];
513 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
514 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
515 node.forcedRefresh = YES;
516 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
518 [browser validateVisibleColumns];
521 - (IBAction)refresh:(id)sender {
522 if (![NSThread isMainThread]) {
523 [self performSelectorOnMainThread:@selector(refresh:) withObject:sender waitUntilDone:NO];
528 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
529 [self forceRefresh:sender];
532 [accountNode refresh];
533 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
534 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
536 [browser validateVisibleColumns];
541 #pragma mark NSBrowserDelegate
543 - (id)rootItemForBrowser:(NSBrowser *)browser {
547 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
548 PithosNode *node = (PithosNode *)item;
549 return node.children.count;
552 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
553 PithosNode *node = (PithosNode *)item;
554 return [node.children objectAtIndex:index];
557 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
558 PithosNode *node = (PithosNode *)item;
559 return node.isLeafItem;
562 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
563 PithosNode *node = (PithosNode *)item;
567 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
568 if (sharedPreviewController == nil)
569 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
570 return sharedPreviewController;
573 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
574 // if (!forUserResize) {
575 // id item = [browser parentForItemsInColumn:columnIndex];
576 // if ([self browser:browser isLeafItem:item]) {
577 // suggestedWidth = 200;
580 // return suggestedWidth;
583 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
589 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
590 PithosNode *node = (PithosNode *)item;
591 if (node.shared || node.sharingAccount ||
592 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
598 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
600 PithosNode *node = (PithosNode *)item;
601 NSString *newName = (NSString *)object;
602 NSUInteger newNameLength = [newName length];
603 NSRange firstSlashRange = [newName rangeOfString:@"/"];
604 if ((newNameLength == 0) ||
605 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
606 ([newName isEqualToString:node.displayName])) {
609 if (([node class] == [PithosObjectNode class]) ||
610 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
611 // Operation: Rename (move) an object or subdir/ node
612 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
614 if (operation.isCancelled)
616 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
617 if ([newName hasSuffix:@"/"])
618 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
619 NSError *error = nil;
621 if ([PithosUtilities objectExistsAtPithos:pithosAccountManager.pithos
622 containerName:node.pithosContainer.name
623 objectName:destinationObjectName
625 isDirectory:&isDirectory
626 sharingAccount:nil]) {
627 dispatch_async(dispatch_get_main_queue(), ^{
628 NSAlert *alert = [[NSAlert alloc] init];
629 [alert setMessageText:@"Name Taken"];
630 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
631 [alert addButtonWithTitle:@"OK"];
638 if (operation.isCancelled)
640 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithosAccountManager.pithos
641 containerName:node.pithosContainer.name
642 objectName:node.pithosObject.name
643 destinationContainerName:node.pithosContainer.name
644 destinationObjectName:destinationObjectName
646 if (!operation.isCancelled && objectRequest) {
647 objectRequest.delegate = self;
648 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
649 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
650 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
651 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
652 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
653 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
654 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
655 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
656 message:messagePrefix];
657 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
658 [NSDictionary dictionaryWithObjectsAndKeys:
659 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
660 [NSNumber numberWithBool:YES], @"refresh",
661 activity, @"activity",
662 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
663 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
664 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
665 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
666 [NSNumber numberWithUnsignedInteger:10], @"retries",
667 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
668 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
669 moveNetworkQueue, @"networkQueue",
670 @"move", @"operationType",
672 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
676 [moveQueue addOperation:operation];
677 } else if ([node class] == [PithosSubdirNode class]) {
678 if (firstSlashRange.length == 1)
680 // Operation: Rename (move) a subdir node and its descendants
681 // The resulting ASIPithosObjectRequests are chained through dependencies
682 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
684 if (operation.isCancelled)
686 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
687 NSError *error = nil;
689 if ([PithosUtilities objectExistsAtPithos:pithosAccountManager.pithos
690 containerName:node.pithosContainer.name
691 objectName:destinationObjectName
693 isDirectory:&isDirectory
694 sharingAccount:nil]) {
695 dispatch_async(dispatch_get_main_queue(), ^{
696 NSAlert *alert = [[NSAlert alloc] init];
697 [alert setMessageText:@"Name Taken"];
698 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
699 [alert addButtonWithTitle:@"OK"];
706 if (operation.isCancelled)
708 if (node.pithosObject.subdir)
709 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
710 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithosAccountManager.pithos
711 containerName:node.pithosContainer.name
712 objectName:node.pithosObject.name
713 destinationContainerName:node.pithosContainer.name
714 destinationObjectName:destinationObjectName
716 if (!operation.isCancelled && objectRequests) {
717 ASIPithosObjectRequest *previousObjectRequest = nil;
718 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
719 if (operation.isCancelled)
721 objectRequest.delegate = self;
722 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
723 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
724 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
725 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
726 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
727 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
728 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
729 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
730 message:messagePrefix];
731 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
732 [NSDictionary dictionaryWithObjectsAndKeys:
733 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
734 [NSNumber numberWithBool:YES], @"refresh",
735 activity, @"activity",
736 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
737 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
738 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
739 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
740 [NSNumber numberWithUnsignedInteger:10], @"retries",
741 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
742 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
743 moveNetworkQueue, @"networkQueue",
744 @"move", @"operationType",
746 if (previousObjectRequest)
747 [objectRequest addDependency:previousObjectRequest];
748 previousObjectRequest = objectRequest;
749 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
754 [moveQueue addOperation:operation];
758 #pragma mark Drag and Drop source
760 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
761 withEvent:(NSEvent *)event {
762 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
763 __block BOOL result = YES;
764 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
765 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
766 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
774 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
775 toPasteboard:(NSPasteboard *)pasteboard {
776 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
777 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
778 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
779 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
780 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
781 [propertyList addObject:[node.pithosObject.name pathExtension]];
782 [nodes addObject:node];
785 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
786 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
787 self.draggedNodes = nodes;
788 self.draggedParentNode = [browser parentForItemsInColumn:column];
792 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
793 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
794 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
795 for (PithosNode *node in draggedNodes) {
796 [names addObject:node.displayName];
797 // If the node is a subdir ask if the whole tree should be downloaded
798 if ([node class] == [PithosSubdirNode class]) {
799 NSAlert *alert = [[NSAlert alloc] init];
800 [alert setMessageText:@"Download directory"];
801 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
802 [alert addButtonWithTitle:@"OK"];
803 [alert addButtonWithTitle:@"Cancel"];
804 NSInteger choice = [alert runModal];
805 if (choice == NSAlertFirstButtonReturn)
806 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
808 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
814 #pragma mark Drag and Drop destination
816 - (NSDragOperation)browser:aBrowser
817 validateDrop:(id<NSDraggingInfo>)info
818 proposedRow:(NSInteger *)row
819 column:(NSInteger *)column
820 dropOperation:(NSBrowserDropOperation *)dropOperation {
821 NSDragOperation result = NSDragOperationNone;
822 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
823 // For a drop above, the drop is redirected to the parent item
824 if (*dropOperation == NSBrowserDropAbove)
826 // Only allow dropping in folders
828 PithosNode *dropNode;
830 // Check if the node is not a folder and if so redirect to the parent item
831 dropNode = [browser itemAtRow:*row inColumn:*column];
832 if ([dropNode class] == [PithosObjectNode class])
836 dropNode = [browser parentForItemsInColumn:*column];
838 if (!dropNode.shared &&
839 (!dropNode.sharingAccount ||
840 ([dropNode class] == [PithosSubdirNode class]) ||
841 ([dropNode class] == [PithosContainerNode class]))) {
842 *dropOperation = NSBrowserDropOn;
843 result = NSDragOperationCopy;
846 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
847 // For a drop above, the drop is redirected to the parent item
848 if (*dropOperation == NSBrowserDropAbove)
850 // Only allow dropping in folders
852 PithosNode *dropNode;
854 // Check if the node is not a folder and if so redirect to the parent item
855 dropNode = [browser itemAtRow:*row inColumn:*column];
856 if ([dropNode class] == [PithosObjectNode class])
860 dropNode = [browser parentForItemsInColumn:*column];
862 if (!dropNode.shared && !dropNode.sharingAccount) {
863 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
864 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
865 if ((([dropNode class] == [PithosContainerNode class]) ||
866 dropNode.pithosObject.subdir ||
867 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
868 ![dropNode isEqualTo:draggedParentNode]) {
869 // ![dropNode isEqualTo:draggedParentNode] &&
870 // ![draggedNodes containsObject:dropNode]) {
871 result = NSDragOperationMove;
873 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
874 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
875 if (([dropNode class] == [PithosContainerNode class]) ||
876 dropNode.pithosObject.subdir ||
877 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
878 result = NSDragOperationCopy;
887 - (BOOL)browser:(NSBrowser *)aBrowser
888 acceptDrop:(id<NSDraggingInfo>)info
890 column:(NSInteger)column
891 dropOperation:(NSBrowserDropOperation)dropOperation {
892 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
893 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
894 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
895 if ((column != -1) && (filenames != nil)) {
898 node = [browser itemAtRow:row inColumn:column];
900 node = [browser parentForItemsInColumn:column];
901 DLog(@"drag in node: %@", node.url);
902 return [self uploadFiles:filenames toNode:node];
904 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
905 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
906 if ((column != -1) && (draggedNodes != nil)) {
909 node = [browser itemAtRow:row inColumn:column];
911 node = [browser parentForItemsInColumn:column];
912 DLog(@"drag local node: %@", node.url);
913 if ([info draggingSourceOperationMask] & NSDragOperationMove)
914 return [self moveNodes:draggedNodes toNode:node];
915 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
916 return [self cpyNodes:draggedNodes toNode:node];
923 #pragma mark NSBrowser Actions
925 - (void)browserDoubleAction:(id)sender {
926 NSInteger column = [browser clickedColumn];
927 NSInteger row = [browser clickedRow];
928 if ((column == -1) || (row == -1))
930 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
931 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
932 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
933 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
934 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
935 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
938 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
940 NSMenuItem *menuItem = [[NSMenuItem alloc] init];
941 menuItem.representedObject = menuNodes;
942 [self menuDownload:menuItem];
946 #pragma mark Drag and Drop methods
948 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
949 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
950 if ([node class] == [PithosSubdirNode class]) {
951 // XXX newFilename and version are ignored in the case of a subdir node for now
952 // Operation: Download a subdir node and its descendants
953 // The resulting ASIPithosObjectRequests are chained through dependencies
954 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
956 if (operation.isCancelled)
958 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithosAccountManager.pithos
959 containerName:node.pithosContainer.name
960 objectName:node.pithosObject.name
962 checkIfExists:checkIfExists
963 sharingAccount:node.sharingAccount];
964 if (!operation.isCancelled && objectRequests) {
965 ASIPithosObjectRequest *previousObjectRequest = nil;
966 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
967 if (operation.isCancelled)
969 objectRequest.delegate = self;
970 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
971 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
972 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
973 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
974 message:[messagePrefix stringByAppendingString:@" (0%)"]
975 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
977 dispatch_async(dispatch_get_main_queue(), ^{
978 [activityFacility updateActivity:activity withMessage:activity.message];
980 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
981 [NSDictionary dictionaryWithObjectsAndKeys:
982 activity, @"activity",
983 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
984 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
985 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
986 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
987 [NSNumber numberWithUnsignedInteger:10], @"retries",
988 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
989 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
990 downloadNetworkQueue, @"networkQueue",
991 @"download", @"operationType",
993 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
994 [activityFacility updateActivity:activity
995 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
996 totalBytes:activity.totalBytes
997 currentBytes:(activity.currentBytes + size)];
999 if (previousObjectRequest)
1000 [objectRequest addDependency:previousObjectRequest];
1001 previousObjectRequest = objectRequest;
1002 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1007 [downloadQueue addOperation:operation];
1008 } else if ([node class] == [PithosObjectNode class]) {
1009 // Operation: Download an object node
1010 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1012 if (operation.isCancelled)
1014 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithosAccountManager.pithos
1015 containerName:node.pithosContainer.name
1016 objectName:node.pithosObject.name
1019 withNewFileName:newFileName
1020 checkIfExists:checkIfExists
1021 sharingAccount:node.sharingAccount];
1022 if (!operation.isCancelled && objectRequest) {
1023 objectRequest.delegate = self;
1024 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1025 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1026 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1027 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1028 message:[messagePrefix stringByAppendingString:@" (0%)"]
1029 totalBytes:node.pithosObject.bytes
1031 dispatch_async(dispatch_get_main_queue(), ^{
1032 [activityFacility updateActivity:activity withMessage:activity.message];
1034 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1035 [NSDictionary dictionaryWithObjectsAndKeys:
1036 activity, @"activity",
1037 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1038 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1039 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1040 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1041 [NSNumber numberWithUnsignedInteger:10], @"retries",
1042 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1043 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1044 downloadNetworkQueue, @"networkQueue",
1045 @"download", @"operationType",
1047 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1048 [activityFacility updateActivity:activity
1049 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1050 totalBytes:activity.totalBytes
1051 currentBytes:(activity.currentBytes + size)];
1053 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1057 [downloadQueue addOperation:operation];
1061 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1062 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1064 NSFileManager *fileManager = [NSFileManager defaultManager];
1065 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1066 NSString *objectNamePrefix;
1067 if ([destinationNode class] == [PithosSubdirNode class])
1068 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1070 objectNamePrefix = [NSString string];
1071 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1072 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithosAccountManager.pithos
1073 containerName:containerName];
1074 [PithosUtilities startAndWaitForRequest:containerRequest];
1075 if ([containerRequest error]) {
1076 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1078 } else if (containerRequest.responseStatusCode != 204) {
1079 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1082 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1083 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1085 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1086 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1088 for (NSString *filePath in filenames) {
1090 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1093 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1094 precomposedStringWithCanonicalMapping];
1095 // Operation: Upload a local file
1096 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1098 if (operation.isCancelled)
1100 NSError *error = nil;
1101 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1102 if (contentType == nil)
1103 contentType = @"application/octet-stream";
1106 DLog(@"contentType detection error: %@", error);
1108 NSArray *hashes = nil;
1109 if (operation.isCancelled)
1111 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithosAccountManager.pithos
1112 containerName:containerName
1113 objectName:objectName
1114 contentType:contentType
1120 sharingAccount:destinationNode.sharingAccount];
1121 if (!operation.isCancelled && objectRequest) {
1122 objectRequest.delegate = self;
1123 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1124 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1125 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1126 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1127 message:[messagePrefix stringByAppendingString:@" (0%)"]
1128 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1130 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1131 [NSDictionary dictionaryWithObjectsAndKeys:
1132 containerName, @"containerName",
1133 objectName, @"objectName",
1134 contentType, @"contentType",
1135 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1136 blockHash, @"blockHash",
1137 filePath, @"filePath",
1139 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1140 [NSNumber numberWithBool:YES], @"refresh",
1141 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1142 activity, @"activity",
1143 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1144 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1145 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1146 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1147 [NSNumber numberWithUnsignedInteger:10], @"retries",
1148 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1149 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1150 uploadNetworkQueue, @"networkQueue",
1151 @"upload", @"operationType",
1153 if (destinationNode.sharingAccount)
1154 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1155 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1159 [uploadQueue addOperation:operation];
1161 // Upload directory, confirm first
1162 NSAlert *alert = [[NSAlert alloc] init];
1163 [alert setMessageText:@"Upload directory"];
1164 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1165 [alert addButtonWithTitle:@"OK"];
1166 [alert addButtonWithTitle:@"Cancel"];
1167 NSInteger choice = [alert runModal];
1168 if (choice == NSAlertFirstButtonReturn) {
1169 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1170 precomposedStringWithCanonicalMapping];
1171 // Operation: Upload a local directory and its descendants
1172 // The resulting ASIPithosObjectRequests are chained through dependencies
1173 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1175 if (operation.isCancelled)
1177 NSMutableArray *objectNames = nil;
1178 NSMutableArray *contentTypes = nil;
1179 NSMutableArray *filePaths = nil;
1180 NSMutableArray *hashesArrays = nil;
1181 NSMutableArray *directoryObjectRequests = nil;
1182 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithosAccountManager.pithos
1183 containerName:containerName
1184 objectName:objectName
1187 forDirectory:filePath
1189 objectNames:&objectNames
1190 contentTypes:&contentTypes
1191 filePaths:&filePaths
1192 hashesArrays:&hashesArrays
1193 directoryObjectRequests:&directoryObjectRequests
1194 sharingAccount:destinationNode.sharingAccount];
1195 if (operation.isCancelled)
1197 ASIPithosObjectRequest *previousObjectRequest = nil;
1198 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1199 if (operation.isCancelled)
1201 objectRequest.delegate = self;
1202 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1203 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1204 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1205 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1206 message:messagePrefix];
1207 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1208 [NSDictionary dictionaryWithObjectsAndKeys:
1209 [NSNumber numberWithBool:YES], @"refresh",
1210 activity, @"activity",
1211 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1212 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1213 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1214 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1215 [NSNumber numberWithUnsignedInteger:10], @"retries",
1216 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1217 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1218 uploadNetworkQueue, @"networkQueue",
1219 @"upload", @"operationType",
1221 if (previousObjectRequest)
1222 [objectRequest addDependency:previousObjectRequest];
1223 previousObjectRequest = objectRequest;
1224 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1226 if (!operation.isCancelled && objectRequests) {
1227 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1228 if (operation.isCancelled)
1230 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1231 objectRequest.delegate = self;
1232 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1233 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1234 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1235 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1236 message:[messagePrefix stringByAppendingString:@" (0%)"]
1237 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1239 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1240 [NSDictionary dictionaryWithObjectsAndKeys:
1241 containerName, @"containerName",
1242 [objectNames objectAtIndex:i], @"objectName",
1243 [contentTypes objectAtIndex:i], @"contentType",
1244 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1245 blockHash, @"blockHash",
1246 [filePaths objectAtIndex:i], @"filePath",
1247 [hashesArrays objectAtIndex:i], @"hashes",
1248 [NSNumber numberWithBool:YES], @"refresh",
1249 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1250 activity, @"activity",
1251 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1252 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1253 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1254 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1255 [NSNumber numberWithUnsignedInteger:10], @"retries",
1256 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1257 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1258 uploadNetworkQueue, @"networkQueue",
1259 @"upload", @"operationType",
1261 if (destinationNode.sharingAccount)
1262 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1263 if (previousObjectRequest)
1264 [objectRequest addDependency:previousObjectRequest];
1265 previousObjectRequest = objectRequest;
1266 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1271 [uploadQueue addOperation:operation];
1279 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1280 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1281 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1283 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1284 NSString *objectNamePrefix;
1285 if ([destinationNode class] == [PithosSubdirNode class])
1286 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1288 objectNamePrefix = [NSString string];
1290 for (PithosNode *node in nodes) {
1291 if (([node class] == [PithosObjectNode class]) ||
1292 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1293 // Operation: Move an object or subdir/ node
1294 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1296 if (operation.isCancelled)
1298 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1299 if ([node.pithosObject.name hasSuffix:@"/"])
1300 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1301 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithosAccountManager.pithos
1302 containerName:node.pithosContainer.name
1303 objectName:node.pithosObject.name
1304 destinationContainerName:containerName
1305 destinationObjectName:destinationObjectName
1307 if (!operation.isCancelled && objectRequest) {
1308 objectRequest.delegate = self;
1309 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1310 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1311 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1312 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1313 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1314 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1315 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1316 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1317 message:messagePrefix];
1318 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1319 [NSDictionary dictionaryWithObjectsAndKeys:
1320 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1321 activity, @"activity",
1322 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1323 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1324 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1325 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1326 [NSNumber numberWithUnsignedInteger:10], @"retries",
1327 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1328 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1329 moveNetworkQueue, @"networkQueue",
1330 @"move", @"operationType",
1332 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1336 [moveQueue addOperation:operation];
1337 } else if ([node class] == [PithosSubdirNode class]) {
1338 // Operation: Move a subdir node and its descendants
1339 // The resulting ASIPithosObjectRequests are chained through dependencies
1340 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1342 if (operation.isCancelled)
1344 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1345 if (node.pithosObject.subdir)
1346 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1347 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithosAccountManager.pithos
1348 containerName:node.pithosContainer.name
1349 objectName:node.pithosObject.name
1350 destinationContainerName:containerName
1351 destinationObjectName:destinationObjectName
1353 if (!operation.isCancelled && objectRequests) {
1354 ASIPithosObjectRequest *previousObjectRequest = nil;
1355 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1356 if (operation.isCancelled)
1358 objectRequest.delegate = self;
1359 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1360 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1361 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1362 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1363 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1364 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1365 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1366 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1367 message:messagePrefix];
1368 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1369 [NSDictionary dictionaryWithObjectsAndKeys:
1370 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1371 [NSNumber numberWithBool:YES], @"refresh",
1372 activity, @"activity",
1373 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1374 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1375 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1376 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1377 [NSNumber numberWithUnsignedInteger:10], @"retries",
1378 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1379 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1380 moveNetworkQueue, @"networkQueue",
1381 @"move", @"operationType",
1383 if (previousObjectRequest)
1384 [objectRequest addDependency:previousObjectRequest];
1385 previousObjectRequest = objectRequest;
1386 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1391 [moveQueue addOperation:operation];
1397 - (BOOL)cpyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1398 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1399 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1401 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1402 NSString *objectNamePrefix;
1403 if ([destinationNode class] == [PithosSubdirNode class])
1404 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1406 objectNamePrefix = [NSString string];
1408 for (PithosNode *node in nodes) {
1409 if (([node class] == [PithosObjectNode class]) ||
1410 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1411 // Operation: Copy an object or subdir/ node
1412 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1414 if (operation.isCancelled)
1416 NSString *destinationObjectName;
1417 if (![destinationNode isEqualTo:node.parent]) {
1418 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1419 if ([node.pithosObject.name hasSuffix:@"/"])
1420 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1422 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithosAccountManager.pithos
1423 containerName:containerName
1424 objectName:node.pithosObject.name];
1426 if (operation.isCancelled)
1428 ASIPithosObjectRequest *objectRequest = [PithosUtilities cpyObjectRequestWithPithos:pithosAccountManager.pithos
1429 containerName:node.pithosContainer.name
1430 objectName:node.pithosObject.name
1431 destinationContainerName:containerName
1432 destinationObjectName:destinationObjectName
1434 sharingAccount:node.sharingAccount];
1435 if (!operation.isCancelled && objectRequest) {
1436 objectRequest.delegate = self;
1437 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1438 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1439 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1440 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1441 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1442 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1443 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1444 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1445 message:messagePrefix];
1446 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1447 [NSDictionary dictionaryWithObjectsAndKeys:
1448 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1449 activity, @"activity",
1450 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1451 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1452 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1453 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1454 [NSNumber numberWithUnsignedInteger:10], @"retries",
1455 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1456 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1457 copyNetworkQueue, @"networkQueue",
1458 @"copy", @"operationType",
1460 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1464 [copyQueue addOperation:operation];
1465 } else if ([node class] == [PithosSubdirNode class]) {
1466 // Operation: Copy a subdir node and its descendants
1467 // The resulting ASIPithosObjectRequests are chained through dependencies
1468 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1470 if (operation.isCancelled)
1472 NSString *destinationObjectName;
1473 if (![destinationNode isEqualTo:node.parent]) {
1474 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1475 if (node.pithosObject.subdir)
1476 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1478 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithosAccountManager.pithos
1479 containerName:containerName
1480 subdirName:node.pithosObject.name];
1482 if (operation.isCancelled)
1484 NSArray *objectRequests = [PithosUtilities cpyObjectRequestsForSubdirWithPithos:pithosAccountManager.pithos
1485 containerName:node.pithosContainer.name
1486 objectName:node.pithosObject.name
1487 destinationContainerName:containerName
1488 destinationObjectName:destinationObjectName
1490 sharingAccount:node.sharingAccount];
1491 if (!operation.isCancelled && objectRequests) {
1492 ASIPithosObjectRequest *previousObjectRequest = nil;
1493 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1494 if (operation.isCancelled)
1496 objectRequest.delegate = self;
1497 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1498 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1499 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1500 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1501 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1502 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1503 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1504 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1505 message:messagePrefix];
1506 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1507 [NSDictionary dictionaryWithObjectsAndKeys:
1508 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1509 activity, @"activity",
1510 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1511 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1512 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1513 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1514 [NSNumber numberWithUnsignedInteger:10], @"retries",
1515 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1516 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1517 copyNetworkQueue, @"networkQueue",
1518 @"copy", @"operationType",
1520 if (previousObjectRequest)
1521 [objectRequest addDependency:previousObjectRequest];
1522 previousObjectRequest = objectRequest;
1523 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1528 [copyQueue addOperation:operation];
1535 #pragma mark ASIHTTPRequestDelegate
1537 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1538 NSOperationQueue *callbackQueue;
1539 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1540 if ([operationType isEqualToString:@"move"])
1541 callbackQueue = moveCallbackQueue;
1542 else if ([operationType isEqualToString:@"copy"])
1543 callbackQueue = copyCallbackQueue;
1544 else if ([operationType isEqualToString:@"delete"])
1545 callbackQueue = deleteCallbackQueue;
1546 else if ([operationType isEqualToString:@"upload"])
1547 callbackQueue = uploadCallbackQueue;
1548 else if ([operationType isEqualToString:@"download"])
1549 callbackQueue = downloadCallbackQueue;
1551 dispatch_async(dispatch_get_main_queue(), ^{
1552 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1553 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1557 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1558 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1559 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1561 operation.completionBlock = ^{
1563 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1564 dispatch_async(dispatch_get_main_queue(), ^{
1565 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1566 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1571 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1572 [callbackQueue addOperation:operation];
1575 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1576 if (request.isCancelled) {
1577 // Request has been cancelled
1578 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1579 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1580 withObject:request];
1582 NSOperationQueue *callbackQueue;
1583 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1584 if ([operationType isEqualToString:@"move"])
1585 callbackQueue = moveCallbackQueue;
1586 else if ([operationType isEqualToString:@"copy"])
1587 callbackQueue = copyCallbackQueue;
1588 else if ([operationType isEqualToString:@"delete"])
1589 callbackQueue = deleteCallbackQueue;
1590 else if ([operationType isEqualToString:@"upload"])
1591 callbackQueue = uploadCallbackQueue;
1592 else if ([operationType isEqualToString:@"download"])
1593 callbackQueue = downloadCallbackQueue;
1595 dispatch_async(dispatch_get_main_queue(), ^{
1596 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1597 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1601 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1602 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
1603 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1605 operation.completionBlock = ^{
1607 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1608 dispatch_async(dispatch_get_main_queue(), ^{
1609 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1610 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1615 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1616 [callbackQueue addOperation:operation];
1620 - (void)requestFailed:(ASIPithosRequest *)request {
1622 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1623 DLog(@"Request failed: %@", request.url);
1624 if (operation.isCancelled)
1626 if (request.isCancelled) {
1627 dispatch_async(dispatch_get_main_queue(), ^{
1628 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1629 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1633 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1635 ASIPithosRequest *newRequest = (ASIPithosRequest *)[PithosUtilities retryWithUpdatedURLRequest:request
1636 andPithosAccountManager:pithosAccountManager];;
1637 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1638 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1639 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1641 dispatch_async(dispatch_get_main_queue(), ^{
1642 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1643 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1645 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1646 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1648 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1653 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1655 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1656 DLog(@"Download finished: %@", objectRequest.url);
1657 if (operation.isCancelled) {
1658 [self requestFailed:objectRequest];
1659 } else if (objectRequest.responseStatusCode == 200) {
1660 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1661 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1662 NSUInteger totalBytes = activity.totalBytes;
1664 // XXX change contentLength to objectContentLength if it is fixed in the server
1665 if ([objectRequest contentLength] == 0) {
1666 // The check above was:
1667 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1668 // I checked for directory content types in order not to create a file in place of a directory,
1669 // but this callback method is not called in the case of a directory download.
1670 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1671 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1672 DLog(@"Downloaded 0 bytes");
1673 NSFileManager *fileManager = [NSFileManager defaultManager];
1674 if (![fileManager fileExistsAtPath:filePath]) {
1675 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1676 dispatch_async(dispatch_get_main_queue(), ^{
1677 NSAlert *alert = [[NSAlert alloc] init];
1678 [alert setMessageText:@"Create File Error"];
1679 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1680 [alert addButtonWithTitle:@"OK"];
1687 NSUInteger currentBytes = [objectRequest objectContentLength];
1688 if (currentBytes == 0)
1689 currentBytes = totalBytes;
1690 dispatch_async(dispatch_get_main_queue(), ^{
1691 [activityFacility endActivity:activity
1692 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1693 totalBytes:totalBytes
1694 currentBytes:currentBytes];
1697 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1698 [self requestFailed:objectRequest];
1703 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1705 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1706 DLog(@"Upload directory object finished: %@", objectRequest.url);
1707 if (operation.isCancelled) {
1708 [self requestFailed:objectRequest];
1709 } else if (objectRequest.responseStatusCode == 201) {
1710 DLog(@"Directory object created: %@", objectRequest.url);
1711 dispatch_async(dispatch_get_main_queue(), ^{
1712 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1713 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1714 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1715 [node forceRefresh];
1717 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1720 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1721 [self forceRefresh:self];
1722 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1723 [self refresh:self];
1726 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1727 [self requestFailed:objectRequest];
1732 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1734 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1735 DLog(@"Upload using hashmap finished: %@", objectRequest.url);
1736 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1737 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1738 NSUInteger totalBytes = activity.totalBytes;
1739 NSUInteger currentBytes = activity.currentBytes;
1740 if (operation.isCancelled) {
1741 [self requestFailed:objectRequest];
1742 } else if (objectRequest.responseStatusCode == 201) {
1743 DLog(@"Object created: %@", objectRequest.url);
1744 dispatch_async(dispatch_get_main_queue(), ^{
1745 [activityFacility endActivity:activity
1746 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1747 totalBytes:totalBytes
1748 currentBytes:totalBytes];
1749 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1750 [node forceRefresh];
1752 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1755 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1756 [self forceRefresh:self];
1757 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1758 [self refresh:self];
1760 } else if (objectRequest.responseStatusCode == 409) {
1761 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1762 if (iteration == 0) {
1763 DLog(@"Upload iteration limit reached: %@", objectRequest.url);
1764 dispatch_async(dispatch_get_main_queue(), ^{
1765 [activityFacility endActivity:activity
1766 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1767 NSAlert *alert = [[NSAlert alloc] init];
1768 [alert setMessageText:@"Upload Timeout"];
1769 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1770 [objectRequest.userInfo objectForKey:@"objectName"]]];
1771 [alert addButtonWithTitle:@"OK"];
1776 DLog(@"object is missing hashes: %@", objectRequest.url);
1777 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1778 withMissingHashes:[objectRequest hashes]];
1779 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1780 if (totalBytes >= [missingBlocks count]*blockSize)
1781 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1782 dispatch_async(dispatch_get_main_queue(), ^{
1783 [activityFacility updateActivity:activity
1784 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1785 totalBytes:totalBytes
1786 currentBytes:currentBytes];
1788 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1789 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithosAccountManager.pithos
1790 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1792 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1793 missingBlockIndex:missingBlockIndex
1794 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1795 newContainerRequest.delegate = self;
1796 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1797 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1798 newContainerRequest.userInfo = objectRequest.userInfo;
1799 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1800 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1801 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1802 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1803 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1804 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1805 [activityFacility updateActivity:activity
1806 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1807 totalBytes:activity.totalBytes
1808 currentBytes:(activity.currentBytes + size)];
1810 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1812 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1813 [self requestFailed:objectRequest];
1818 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1820 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1821 DLog(@"Upload of missing block finished: %@", containerRequest.url);
1822 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1823 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1824 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1825 if (operation.isCancelled) {
1826 [self requestFailed:containerRequest];
1827 } else if (containerRequest.responseStatusCode == 202) {
1828 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1829 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1830 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1831 if (missingBlockIndex == NSNotFound) {
1832 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1833 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithosAccountManager.pithos
1834 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1835 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1836 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1838 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1839 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1842 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1843 newObjectRequest.delegate = self;
1844 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1845 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1846 newObjectRequest.userInfo = containerRequest.userInfo;
1847 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1848 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1849 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1850 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1851 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1853 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithosAccountManager.pithos
1854 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1855 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1856 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1857 missingBlockIndex:missingBlockIndex
1858 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1859 newContainerRequest.delegate = self;
1860 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1861 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1862 newContainerRequest.userInfo = containerRequest.userInfo;
1863 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1864 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1865 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1866 [activityFacility updateActivity:activity
1867 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1868 totalBytes:activity.totalBytes
1869 currentBytes:(activity.currentBytes + size)];
1871 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1874 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1875 [self requestFailed:containerRequest];
1880 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1882 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1883 DLog(@"Move object finished: %@", objectRequest.url);
1884 if (operation.isCancelled) {
1885 [self requestFailed:objectRequest];
1886 } else if (objectRequest.responseStatusCode == 201) {
1887 dispatch_async(dispatch_get_main_queue(), ^{
1888 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1889 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1890 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1891 [node forceRefresh];
1893 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1896 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1897 [self forceRefresh:self];
1898 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1899 [self refresh:self];
1902 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1903 [self requestFailed:objectRequest];
1908 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1910 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1911 DLog(@"Copy object finished: %@", objectRequest.url);
1912 if (operation.isCancelled) {
1913 [self requestFailed:objectRequest];
1914 } else if (objectRequest.responseStatusCode == 201) {
1915 dispatch_async(dispatch_get_main_queue(), ^{
1916 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1917 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1918 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1919 [node forceRefresh];
1921 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1924 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1925 [self forceRefresh:self];
1926 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1927 [self refresh:self];
1930 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1931 [self requestFailed:objectRequest];
1936 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1938 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1939 DLog(@"Delete object finished: %@", objectRequest.url);
1940 if (operation.isCancelled) {
1941 [self requestFailed:objectRequest];
1942 } else if (objectRequest.responseStatusCode == 204) {
1943 dispatch_async(dispatch_get_main_queue(), ^{
1944 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1945 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1946 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1947 [node forceRefresh];
1949 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1952 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1953 [self forceRefresh:self];
1954 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1955 [self refresh:self];
1958 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1959 [self requestFailed:objectRequest];
1965 #pragma mark NSSplitViewDelegate
1967 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1968 if (splitView == verticalSplitView)
1971 return ([horizontalSplitView bounds].size.height - 142);
1974 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1975 if (splitView == verticalSplitView)
1978 return ([horizontalSplitView bounds].size.height - 108);
1981 - (BOOL)splitView:(NSSplitView *)splitView shouldAdjustSizeOfSubview:(NSView *)view {
1982 if (((splitView == verticalSplitView) && (view == leftView)) ||
1983 ((splitView == horizontalSplitView) && (view == leftBottomView))) {
1990 #pragma mark NSOutlineViewDataSource
1992 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1993 if (!browserInitialized)
1997 if (item == containersNode)
1998 return containersNodeChildren.count;
1999 if (item == sharedNode)
2004 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2005 if (!browserInitialized)
2008 return (!index ? containersNode : sharedNode);
2009 if (item == sharedNode)
2010 return (!index ? mySharedNode : othersSharedNode);
2011 return [containersNodeChildren objectAtIndex:index];
2014 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2015 if ((item == containersNode) || (item == sharedNode))
2020 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2021 PithosNode *node = (PithosNode *)item;
2025 #pragma mark Drag and Drop destination
2027 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2028 validateDrop:(id<NSDraggingInfo>)info
2029 proposedItem:(id)item
2030 proposedChildIndex:(NSInteger)index {
2031 NSDragOperation result = NSDragOperationNone;
2032 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2034 PithosNode *dropNode = (PithosNode *)item;
2035 if ([dropNode class] != [PithosContainerNode class])
2037 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2038 result = NSDragOperationCopy;
2039 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2040 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2041 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2042 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2043 if (![dropNode isEqualTo:draggedParentNode])
2044 result = NSDragOperationMove;
2045 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2046 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2047 result = NSDragOperationCopy;
2053 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2054 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2055 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2056 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2057 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2058 PithosNode *node = (PithosNode *)item;
2059 DLog(@"drag in node: %@", node.url);
2060 return [self uploadFiles:filenames toNode:node];
2062 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2063 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2064 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2065 PithosNode *node = (PithosNode *)item;
2066 DLog(@"drag local node: %@", node.url);
2067 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2068 ([info draggingSourceOperationMask] & NSDragOperationMove))
2069 return [self moveNodes:draggedNodes toNode:node];
2070 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2071 return [self cpyNodes:draggedNodes toNode:node];
2078 #pragma mark NSOutlineViewDelegate
2080 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2081 if ((item == containersNode) || (item == sharedNode))
2086 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2087 if ((item == containersNode) || (item == sharedNode))
2092 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2093 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2096 [browser loadColumnZero];
2102 #pragma mark NSMenuDelegate
2104 - (void)menuNeedsUpdate:(NSMenu *)menu {
2105 [menu removeAllItems];
2106 NSMenuItem *menuItem;
2107 NSString *menuItemTitle;
2108 BOOL nodeContextMenu = NO;
2109 PithosNode *menuNode = nil;
2110 NSMutableArray *menuNodes;
2111 if (menu == browserMenu) {
2112 NSInteger column = [browser clickedColumn];
2113 NSInteger row = [browser clickedRow];
2114 if ((column == -1) || (row == -1)) {
2116 // General context menu
2117 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2118 if ([menuNodesIndexPaths count] == 0) {
2119 menuNode = [browser parentForItemsInColumn:0];
2120 } else if (([menuNodesIndexPaths count] != 1) ||
2121 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2122 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2124 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2127 menuNode = [browser parentForItemsInColumn:column];
2128 if ([menuNode class] == [PithosObjectNode class]) {
2129 // Node context menu
2130 menuNodes = [NSMutableArray arrayWithObject:menuNode];
2131 nodeContextMenu = YES;
2134 // General context menu
2137 // Node context menu
2138 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2139 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2140 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2141 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2142 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2143 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2146 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2148 nodeContextMenu = YES;
2150 } else if (menu == outlineViewMenu) {
2151 NSInteger row = [outlineView clickedRow];
2153 row = [outlineView selectedRow];
2156 menuNode = [outlineView itemAtRow:row];
2159 if (!nodeContextMenu) {
2160 // General context menu
2161 if (([menuNode class] == [PithosAccountNode class]) ||
2162 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2163 ([menuNode class] == [PithosEmptyNode class]))
2166 if (!menuNode.shared && !menuNode.sharingAccount) {
2167 menuItem = [[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""];
2168 [menuItem setRepresentedObject:menuNode];
2169 [menu addItem:menuItem];
2170 [menu addItem:[NSMenuItem separatorItem]];
2173 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2174 [menu addItem:menuItem];
2175 [menu addItem:[NSMenuItem separatorItem]];
2177 menuItem = [[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2178 action:@selector(menuGetInfo:)
2180 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2181 [menu addItem:menuItem];
2183 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2184 (([menuNode class] == [PithosContainerNode class]) ||
2185 (([menuNode class] == [PithosSubdirNode class]) &&
2186 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2187 NSUInteger clipboardNodesCount = [clipboardNodes count];
2188 if (clipboardNodesCount == 0) {
2189 self.clipboardNodes = nil;
2190 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2191 if (clipboardNodesCount == 1)
2192 menuItemTitle = @"Paste Item";
2194 menuItemTitle = @"Paste Items";
2195 [menu addItem:[NSMenuItem separatorItem]];
2196 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2197 [menuItem setRepresentedObject:menuNode];
2198 [menu addItem:menuItem];
2202 // Node context menu
2203 NSUInteger menuNodesCount = [menuNodes count];
2204 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2206 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2207 menuItem = [[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""];
2208 [menuItem setRepresentedObject:menuNodes];
2209 [menu addItem:menuItem];
2210 [menu addItem:[NSMenuItem separatorItem]];
2212 // Move to Trash (pithos container only)
2214 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2215 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2216 menuItem = [[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2217 action:@selector(menuMoveToTrash:)
2219 [menuItem setRepresentedObject:menuNodes];
2220 [menu addItem:menuItem];
2222 menuItem = [[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""];
2223 [menuItem setRepresentedObject:menuNodes];
2224 [menu addItem:menuItem];
2225 [menu addItem:[NSMenuItem separatorItem]];
2228 menuItem = [[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""];
2229 [menu addItem:menuItem];
2231 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2232 [menu addItem:[NSMenuItem separatorItem]];
2233 menuItem = [[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2234 action:@selector(menuGetInfo:)
2236 [menuItem setRepresentedObject:menuNodes];
2237 [menu addItem:menuItem];
2239 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2240 [menu addItem:[NSMenuItem separatorItem]];
2243 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2244 if (menuNodesCount == 1)
2245 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2247 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2248 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""];
2249 [menuItem setRepresentedObject:menuNodes];
2250 [menu addItem:menuItem];
2253 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2254 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2255 if (menuNodesCount == 1)
2256 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2258 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2259 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""];
2260 [menuItem setRepresentedObject:menuNodes];
2261 [menu addItem:menuItem];
2264 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2265 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2266 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2267 NSUInteger clipboardNodesCount = [clipboardNodes count];
2268 if (clipboardNodesCount == 0) {
2269 self.clipboardNodes = nil;
2270 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2271 if (clipboardNodesCount == 1)
2272 menuItemTitle = @"Paste Item";
2274 menuItemTitle = @"Paste Items";
2275 menuItem = [[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""];
2276 [menuItem setRepresentedObject:firstMenuNode];
2277 [menu addItem:menuItem];
2284 #pragma mark NSMenuValidation
2286 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2287 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2288 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2289 if ([menuNodesIndexPaths count] == 0)
2292 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2293 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2294 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2295 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2296 ((menuItem.action == @selector(delete:)) &&
2297 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2298 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2301 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2302 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2303 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2305 menuItem.representedObject = menuNodes;
2306 } else if (menuItem.action == @selector(paste:)) {
2307 if (!clipboardNodes || ![clipboardNodes count])
2310 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2311 PithosNode *menuNode;
2312 if ([menuNodesIndexPaths count] == 0)
2313 menuNode = [browser parentForItemsInColumn:0];
2314 else if (([menuNodesIndexPaths count] != 1) ||
2315 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2316 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2318 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2320 if (menuNode.shared || menuNode.sharingAccount ||
2321 (([menuNode class] != [PithosContainerNode class]) &&
2322 (([menuNode class] != [PithosSubdirNode class]) ||
2323 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2324 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2327 menuItem.representedObject = menuNode;
2332 - (void)cut:(NSMenuItem *)sender {
2333 [self menuCut:sender];
2336 - (void)copy:(NSMenuItem *)sender {
2337 [self menuCopy:sender];
2340 - (void)paste:(NSMenuItem *)sender {
2341 [self menuPaste:sender];
2344 - (void)delete:(NSMenuItem *)sender {
2345 if (sender.tag == 0)
2346 [self menuMoveToTrash:sender];
2348 [self menuDelete:sender];
2352 #pragma mark Menu Actions
2354 - (void)menuNewFolder:(NSMenuItem *)sender {
2355 PithosNode *node = (PithosNode *)[sender representedObject];
2356 if ([node class] == [PithosContainerNode class]) {
2357 // Operation: Create (upload) a new root application/directory object
2358 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2360 if (operation.isCancelled)
2362 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithosAccountManager.pithos
2363 containerName:node.pithosContainer.name
2364 subdirName:@"untitled folder"];
2365 NSString *fileName = [safeObjectName lastPathComponent];
2366 if (operation.isCancelled)
2368 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithosAccountManager.pithos
2369 containerName:node.pithosContainer.name
2370 objectName:safeObjectName
2372 contentType:@"application/directory"
2374 contentDisposition:nil
2377 isPublic:ASIPithosObjectRequestPublicIgnore
2379 data:[NSData data]];
2380 objectRequest.delegate = self;
2381 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2382 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2383 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2384 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2385 message:messagePrefix];
2386 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2387 fileName, @"fileName",
2388 [NSArray arrayWithObject:node], @"refreshNodes",
2389 [NSNumber numberWithBool:YES], @"refresh",
2390 activity, @"activity",
2391 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2392 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2393 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2394 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2395 [NSNumber numberWithUnsignedInteger:10], @"retries",
2396 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2397 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2398 uploadNetworkQueue, @"networkQueue",
2399 @"upload", @"operationType",
2401 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2404 [uploadQueue addOperation:operation];
2405 } else if (([node class] == [PithosSubdirNode class]) &&
2406 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2407 // Operation: Create (upload) a new aplication/directory object
2408 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2410 if (operation.isCancelled)
2412 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithosAccountManager.pithos
2413 containerName:node.pithosContainer.name
2414 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2415 NSString *fileName = [safeObjectName lastPathComponent];
2416 if (operation.isCancelled)
2418 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithosAccountManager.pithos
2419 containerName:node.pithosContainer.name
2420 objectName:safeObjectName
2422 contentType:@"application/directory"
2424 contentDisposition:nil
2427 isPublic:ASIPithosObjectRequestPublicIgnore
2429 data:[NSData data]];
2430 objectRequest.delegate = self;
2431 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2432 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2433 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2434 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2435 message:messagePrefix];
2436 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2437 fileName, @"fileName",
2438 [NSArray arrayWithObject:node], @"refreshNodes",
2439 [NSNumber numberWithBool:YES], @"refresh",
2440 activity, @"activity",
2441 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2442 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2443 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2444 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2445 [NSNumber numberWithUnsignedInteger:10], @"retries",
2446 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2447 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2448 uploadNetworkQueue, @"networkQueue",
2449 @"upload", @"operationType",
2451 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2454 [uploadQueue addOperation:operation];
2458 - (void)menuGetInfo:(NSMenuItem *)sender {
2459 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2460 [node showPithosNodeInfo:sender];
2464 - (void)menuDownload:(NSMenuItem *)sender {
2465 NSArray *nodes = (NSArray *)[sender representedObject];
2466 PithosNode *firstNode = [nodes objectAtIndex:0];
2467 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2468 NSSavePanel *save = [NSSavePanel savePanel];
2469 save.nameFieldStringValue = firstNode.displayName;
2470 NSInteger result = [save runModal];
2471 if (result == NSOKButton) {
2472 NSString *destinationPath = save.URL.path;
2473 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2474 NSString *newFileName = [destinationPath lastPathComponent];
2475 if ([destinationPath hasSuffix:@"/"])
2476 newFileName = [newFileName stringByAppendingString:@"/"];
2477 if ([firstNode.displayName isEqualToString:newFileName])
2479 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2482 NSOpenPanel *open = [NSOpenPanel openPanel];
2483 open.canChooseFiles = NO;
2484 open.canChooseDirectories = YES;
2485 open.canCreateDirectories = YES;
2486 NSInteger result = [open runModal];
2487 if (result == NSOKButton) {
2488 NSString *directoryPath = open.URL.path;
2489 for (PithosNode *node in nodes) {
2490 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2496 - (void)menuDelete:(NSMenuItem *)sender {
2497 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2498 if (([node class] == [PithosObjectNode class]) ||
2499 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2500 // Operation: Delete an object or subdir/ node
2501 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2503 if (operation.isCancelled)
2505 NSString *fileName = [node.pithosObject.name lastPathComponent];
2506 if ([node.pithosObject.name hasSuffix:@"/"])
2507 fileName = [fileName stringByAppendingString:@"/"];
2508 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithosAccountManager.pithos
2509 containerName:node.pithosContainer.name
2510 objectName:node.pithosObject.name];
2511 if (operation.isCancelled)
2513 objectRequest.delegate = self;
2514 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2515 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2516 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2517 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2518 message:messagePrefix];
2519 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2520 fileName, @"fileName",
2521 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2522 activity, @"activity",
2523 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2524 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2525 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2526 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2527 [NSNumber numberWithUnsignedInteger:10], @"retries",
2528 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2529 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2530 deleteNetworkQueue, @"networkQueue",
2531 @"delete", @"operationType",
2533 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2536 [deleteQueue addOperation:operation];
2537 } else if ([node class] == [PithosSubdirNode class]) {
2538 // Operation: Delete a subdir node and its descendants
2539 // The resulting ASIPithosObjectRequests are chained through dependencies
2540 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2542 if (operation.isCancelled)
2544 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithosAccountManager.pithos
2545 containerName:node.pithosContainer.name
2546 objectName:node.pithosObject.name];
2547 if (!operation.isCancelled && objectRequests) {
2548 ASIPithosObjectRequest *previousObjectRequest = nil;
2549 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2550 if (operation.isCancelled)
2552 objectRequest.delegate = self;
2553 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2554 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2555 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2556 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2557 message:messagePrefix];
2558 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2559 [NSDictionary dictionaryWithObjectsAndKeys:
2560 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2561 activity, @"activity",
2562 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2563 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2564 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2565 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2566 [NSNumber numberWithUnsignedInteger:10], @"retries",
2567 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2568 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2569 deleteNetworkQueue, @"networkQueue",
2570 @"delete", @"operationType",
2572 if (previousObjectRequest)
2573 [objectRequest addDependency:previousObjectRequest];
2574 previousObjectRequest = objectRequest;
2575 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2580 [deleteQueue addOperation:operation];
2585 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2586 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2587 if (([node class] == [PithosObjectNode class]) ||
2588 (([node class] == [PithosSubdirNode class]) &&
2589 !node.pithosObject.subdir &&
2590 [node.pithosObject.name hasSuffix:@"/"])) {
2591 // Operation: Move to trash an object or subdir/ node
2592 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2594 if (operation.isCancelled)
2596 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithosAccountManager.pithos
2597 containerName:@"trash"
2598 objectName:node.pithosObject.name];
2599 if (!operation.isCancelled && safeObjectName) {
2600 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithosAccountManager.pithos
2601 containerName:node.pithosContainer.name
2602 objectName:node.pithosObject.name
2603 destinationContainerName:@"trash"
2604 destinationObjectName:safeObjectName
2606 if (!operation.isCancelled && objectRequest) {
2607 objectRequest.delegate = self;
2608 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2609 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2610 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2611 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2612 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2613 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2614 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2615 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2616 message:messagePrefix];
2617 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2618 [NSDictionary dictionaryWithObjectsAndKeys:
2619 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2620 activity, @"activity",
2621 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2622 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2623 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2624 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2625 [NSNumber numberWithUnsignedInteger:10], @"retries",
2626 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2627 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2628 moveNetworkQueue, @"networkQueue",
2629 @"move", @"operationType",
2631 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2636 [moveQueue addOperation:operation];
2637 } else if ([node class] == [PithosSubdirNode class]) {
2638 // Operation: Move to trash a subdir node and its descendants
2639 // The resulting ASIPithosObjectRequests are chained through dependencies
2640 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2642 if (operation.isCancelled)
2644 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithosAccountManager.pithos
2645 containerName:@"trash"
2646 subdirName:node.pithosObject.name];
2647 if (!operation.isCancelled && safeObjectName) {
2648 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithosAccountManager.pithos
2649 containerName:node.pithosContainer.name
2650 objectName:node.pithosObject.name
2651 destinationContainerName:@"trash"
2652 destinationObjectName:safeObjectName
2654 if (!operation.isCancelled && objectRequests) {
2655 ASIPithosObjectRequest *previousObjectRequest = nil;
2656 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2657 if (operation.isCancelled)
2659 objectRequest.delegate = self;
2660 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2661 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2662 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2663 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2664 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2665 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2666 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2667 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2668 message:messagePrefix];
2669 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2670 [NSDictionary dictionaryWithObjectsAndKeys:
2671 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2672 activity, @"activity",
2673 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2674 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2675 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2676 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2677 [NSNumber numberWithUnsignedInteger:10], @"retries",
2678 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2679 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2680 moveNetworkQueue, @"networkQueue",
2681 @"move", @"operationType",
2683 if (previousObjectRequest)
2684 [objectRequest addDependency:previousObjectRequest];
2685 previousObjectRequest = objectRequest;
2686 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2692 [moveQueue addOperation:operation];
2697 - (void)menuCut:(NSMenuItem *)sender {
2698 self.clipboardNodes = (NSArray *)[sender representedObject];
2699 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2700 self.clipboardCopy = NO;
2703 - (void)menuCopy:(NSMenuItem *)sender {
2704 self.clipboardNodes = (NSArray *)[sender representedObject];
2705 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2706 self.clipboardCopy = YES;
2709 - (void)menuPaste:(NSMenuItem *)sender {
2710 if (!clipboardNodes || ![clipboardNodes count])
2712 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2713 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2714 if (clipboardCopy) {
2715 [self cpyNodes:localClipboardNodes toNode:dropNode];
2716 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2717 self.clipboardNodes = nil;
2718 self.clipboardParentNode = nil;
2719 [self moveNodes:localClipboardNodes toNode:dropNode];
2724 #pragma mark PithosActivityFacilityDelegate
2726 - (void)activityUpdate:(NSDictionary *)info {
2727 NSString *message = [info objectForKey:@"message"];
2728 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2729 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2730 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2731 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2732 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2733 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2734 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2735 if (runningActivitiesCount && totalBytes) {
2736 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2737 [activityProgressIndicator startAnimation:self];
2739 [activityProgressIndicator setDoubleValue:1.0];
2740 [activityProgressIndicator stopAnimation:self];
2744 message = [[[UsingSizeTransformer alloc] init] transformedValue:accountNode.pithosAccount];
2745 [activityTextField setStringValue:message];