2 // PithosBrowserController.m
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosSharingAccountsNode.h"
45 #import "PithosEmptyNode.h"
46 #import "ImageAndTextCell.h"
47 #import "FileSystemBrowserCell.h"
48 #import "ASINetworkQueue.h"
49 #import "ASIPithosRequest.h"
51 #import "ASIPithosContainerRequest.h"
52 #import "ASIPithosObjectRequest.h"
53 #import "ASIPithosAccount.h"
54 #import "ASIPithosContainer.h"
55 #import "ASIPithosObject.h"
56 #import "PithosUtilities.h"
57 #import "UsingSizeTransformer.h"
59 #define REFRESH_TIMER_INTERVAL 5
61 @interface PithosBrowserCell : FileSystemBrowserCell {}
64 @implementation PithosBrowserCell
67 if ((self = [super init])) {
68 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
69 [self setEditable:YES];
74 - (void)setObjectValue:(id)object {
75 if ([object isKindOfClass:[PithosNode class]]) {
76 PithosNode *node = (PithosNode *)object;
77 [self setStringValue:node.displayName];
78 [self setImage:node.icon];
80 [super setObjectValue:object];
86 @interface PithosOutlineViewCell : ImageAndTextCell {}
89 @implementation PithosOutlineViewCell
91 - (void)setObjectValue:(id)object {
92 if ([object isKindOfClass:[PithosNode class]]) {
93 PithosNode *node = (PithosNode *)object;
94 [self setStringValue:node.displayName];
95 [self setImage:node.icon];
96 [self setEditable:NO];
98 [super setObjectValue:object];
104 @interface PithosBrowserController (Private)
105 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
106 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
107 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
110 @implementation PithosBrowserController
112 @synthesize accountNode;
113 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu;
114 @synthesize draggedNodes, draggedParentNode;
115 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
116 @synthesize activityTextField, activityProgressIndicator;
119 #pragma Object Lifecycle
122 return [super initWithWindowNibName:@"PithosBrowserController"];
125 - (void)windowDidLoad {
126 [super windowDidLoad];
127 if (browser && !browserInitialized) {
128 browserInitialized = YES;
133 - (void)initBrowser {
134 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
135 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
136 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
138 [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
139 [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
140 [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
142 [browser setCellClass:[PithosBrowserCell class]];
143 [browser setAllowsBranchSelection:YES];
144 [browser setAllowsMultipleSelection:YES];
145 [browser setAllowsEmptySelection:YES];
146 [browser setAllowsTypeSelect:YES];
147 [browser setDoubleAction:@selector(browserDoubleAction:)];
149 moveNetworkQueue = [[ASINetworkQueue alloc] init];
150 moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
151 // moveNetworkQueue.maxConcurrentOperationCount = 1;
152 copyNetworkQueue = [[ASINetworkQueue alloc] init];
153 copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
154 // copyNetworkQueue.maxConcurrentOperationCount = 1;
155 deleteNetworkQueue = [[ASINetworkQueue alloc] init];
156 deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
157 // deleteNetworkQueue.maxConcurrentOperationCount = 1;
158 uploadNetworkQueue = [[ASINetworkQueue alloc] init];
159 uploadNetworkQueue.showAccurateProgress = YES;
160 uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
161 // uploadNetworkQueue.maxConcurrentOperationCount = 1;
162 downloadNetworkQueue = [[ASINetworkQueue alloc] init];
163 downloadNetworkQueue.showAccurateProgress = YES;
164 downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
165 // downloadNetworkQueue.maxConcurrentOperationCount = 1;
167 moveQueue = [[NSOperationQueue alloc] init];
168 [moveQueue setSuspended:YES];
169 moveQueue.name = @"gr.grnet.pithos.MoveQueue";
170 // moveQueue.maxConcurrentOperationCount = 1;
171 copyQueue = [[NSOperationQueue alloc] init];
172 [copyQueue setSuspended:YES];
173 copyQueue.name = @"gr.grnet.pithos.CopyQueue";
174 // copyQueue.maxConcurrentOperationCount = 1;
175 deleteQueue = [[NSOperationQueue alloc] init];
176 [deleteQueue setSuspended:YES];
177 deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
178 // deleteQueue.maxConcurrentOperationCount = 1;
179 uploadQueue = [[NSOperationQueue alloc] init];
180 [uploadQueue setSuspended:YES];
181 uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
182 // uploadQueue.maxConcurrentOperationCount = 1;
183 downloadQueue = [[NSOperationQueue alloc] init];
184 [downloadQueue setSuspended:YES];
185 downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
186 // downloadQueue.maxConcurrentOperationCount = 1;
188 moveCallbackQueue = [[NSOperationQueue alloc] init];
189 [moveCallbackQueue setSuspended:YES];
190 moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
191 // moveCallbackQueue.maxConcurrentOperationCount = 1;
192 copyCallbackQueue = [[NSOperationQueue alloc] init];
193 [copyCallbackQueue setSuspended:YES];
194 copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
195 // copyCallbackQueue.maxConcurrentOperationCount = 1;
196 deleteCallbackQueue = [[NSOperationQueue alloc] init];
197 [deleteCallbackQueue setSuspended:YES];
198 deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
199 // deleteCallbackQueue.maxConcurrentOperationCount = 1;
200 uploadCallbackQueue = [[NSOperationQueue alloc] init];
201 [uploadCallbackQueue setSuspended:YES];
202 uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
203 // uploadCallbackQueue.maxConcurrentOperationCount = 1;
204 downloadCallbackQueue = [[NSOperationQueue alloc] init];
205 [downloadCallbackQueue setSuspended:YES];
206 downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
207 // downloadCallbackQueue.maxConcurrentOperationCount = 1;
209 [activityProgressIndicator setUsesThreadedAnimation:YES];
210 [activityProgressIndicator setMinValue:0.0];
211 [activityProgressIndicator setMaxValue:1.0];
212 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
214 self.accountNode = [[[PithosAccountNode alloc] initWithPithos:pithos] autorelease];
215 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
216 containersNodeChildren = [[NSMutableArray alloc] init];
217 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
218 mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
219 mySharedNode.displayName = @"shared by me";
220 mySharedNode.shared = YES;
221 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
222 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
223 othersSharedNode.displayName = @"shared to me";
224 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
226 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
228 // Register for updates
229 // PithosAccountNode accountNode updates outlineView container nodes
230 [[NSNotificationCenter defaultCenter] addObserver:self
231 selector:@selector(pithosAccountNodeChildrenUpdated:)
232 name:@"PithosNodeChildrenUpdated"
234 // PithosNode updates browser nodes
235 [[NSNotificationCenter defaultCenter] addObserver:self
236 selector:@selector(pithosNodeChildrenUpdated:)
237 name:@"PithosNodeChildrenUpdated"
239 // Request for browser refresh
240 [[NSNotificationCenter defaultCenter] addObserver:self
241 selector:@selector(pithosBrowserRefreshNeeded:)
242 name:@"PithosBrowserRefreshNeeeded"
246 - (void)resetBrowser {
247 @synchronized(self) {
252 [refreshTimer invalidate];
253 [refreshTimer release];
255 [moveNetworkQueue reset];
256 [copyNetworkQueue reset];
257 [deleteNetworkQueue reset];
258 [uploadNetworkQueue reset];
259 [downloadNetworkQueue reset];
261 [moveQueue cancelAllOperations];
262 [moveQueue setSuspended:YES];
263 [copyQueue cancelAllOperations];
264 [copyQueue setSuspended:YES];
265 [deleteQueue cancelAllOperations];
266 [deleteQueue setSuspended:YES];
267 [uploadQueue cancelAllOperations];
268 [uploadQueue setSuspended:YES];
269 [downloadQueue cancelAllOperations];
270 [downloadQueue setSuspended:YES];
272 [moveCallbackQueue cancelAllOperations];
273 [moveCallbackQueue setSuspended:YES];
274 [copyCallbackQueue cancelAllOperations];
275 [copyCallbackQueue setSuspended:YES];
276 [deleteCallbackQueue cancelAllOperations];
277 [deleteCallbackQueue setSuspended:YES];
278 [uploadCallbackQueue cancelAllOperations];
279 [uploadCallbackQueue setSuspended:YES];
280 [downloadCallbackQueue cancelAllOperations];
281 [downloadCallbackQueue setSuspended:YES];
284 [browser loadColumnZero];
285 [containersNodeChildren removeAllObjects];
286 [outlineView reloadData];
287 // Expand the folder outline view
288 [outlineView expandItem:nil expandChildren:YES];
289 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
291 activityFacility.delegate = nil;
292 [activityProgressIndicator setDoubleValue:1.0];
293 [activityProgressIndicator stopAnimation:self];
295 @synchronized(self) {
300 - (void)startBrowser {
301 @synchronized(self) {
306 // In the improbable case of leftover operations
307 [moveNetworkQueue reset];
308 [copyNetworkQueue reset];
309 [deleteNetworkQueue reset];
310 [uploadNetworkQueue reset];
311 [downloadNetworkQueue reset];
312 [moveQueue cancelAllOperations];
313 [copyQueue cancelAllOperations];
314 [deleteQueue cancelAllOperations];
315 [uploadQueue cancelAllOperations];
316 [downloadQueue cancelAllOperations];
317 [moveCallbackQueue cancelAllOperations];
318 [copyCallbackQueue cancelAllOperations];
319 [deleteCallbackQueue cancelAllOperations];
320 [uploadCallbackQueue cancelAllOperations];
321 [downloadCallbackQueue cancelAllOperations];
323 [moveNetworkQueue go];
324 [copyNetworkQueue go];
325 [deleteNetworkQueue go];
326 [uploadNetworkQueue go];
327 [downloadNetworkQueue go];
328 [moveQueue setSuspended:NO];
329 [copyQueue setSuspended:NO];
330 [deleteQueue setSuspended:NO];
331 [uploadQueue setSuspended:NO];
332 [downloadQueue setSuspended:NO];
333 [moveCallbackQueue setSuspended:NO];
334 [copyCallbackQueue setSuspended:NO];
335 [deleteCallbackQueue setSuspended:NO];
336 [uploadCallbackQueue setSuspended:NO];
337 [downloadCallbackQueue setSuspended:NO];
339 accountNode.pithos = pithos;
340 [accountNode forceRefresh];
341 mySharedNode.pithos = pithos;
342 [mySharedNode forceRefresh];
343 othersSharedNode.pithos = pithos;
344 [othersSharedNode forceRefresh];
346 // [activityFacility reset];
347 activityFacility.delegate = self;
349 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
351 selector:@selector(forceRefresh:)
353 repeats:YES] retain];
354 @synchronized(self) {
359 - (BOOL)operationsPending {
360 return ([moveNetworkQueue operationCount] ||
361 [copyNetworkQueue operationCount] ||
362 [deleteNetworkQueue operationCount] ||
363 [uploadNetworkQueue operationCount] ||
364 [downloadNetworkQueue operationCount] ||
365 [moveQueue operationCount] ||
366 [copyQueue operationCount] ||
367 [deleteQueue operationCount] ||
368 [uploadQueue operationCount] ||
369 [downloadQueue operationCount] ||
370 [moveCallbackQueue operationCount] ||
371 [copyCallbackQueue operationCount] ||
372 [deleteCallbackQueue operationCount] ||
373 [uploadCallbackQueue operationCount] ||
374 [downloadCallbackQueue operationCount]);
378 [[NSNotificationCenter defaultCenter] removeObserver:self];
382 [deleteQueue release];
383 [uploadQueue release];
384 [downloadQueue release];
385 [moveNetworkQueue release];
386 [copyNetworkQueue release];
387 [deleteNetworkQueue release];
388 [uploadNetworkQueue release];
389 [downloadNetworkQueue release];
390 [clipboardParentNode release];
391 [clipboardNodes release];
392 [draggedParentNode release];
393 [draggedNodes release];
394 [sharedPreviewController release];
395 [othersSharedNode release];
396 [mySharedNode release];
397 [sharedNode release];
398 [containersNodeChildren release];
399 [containersNode release];
400 [accountNode release];
406 - (void)setPithos:(ASIPithos *)aPithos {
408 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
409 ![aPithos.authToken isEqualToString:pithos.authToken] ||
410 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
411 ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
414 pithos = [aPithos retain];
424 #pragma mark Observers
426 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
427 PithosNode *node = (PithosNode *)[notification object];
428 if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
430 DLog(@"pithosNodeChildrenUpdated:%@", node.url);
431 NSInteger lastColumn = [browser lastColumn];
432 for (NSInteger column = lastColumn; column >= 0; column--) {
433 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
434 [browser reloadColumn:column];
440 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
441 BOOL containerPithosFound = NO;
442 BOOL containerTrashFound = NO;
443 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
444 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
445 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
446 [removedContainersNodeChildren addIndex:i];
448 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
449 for (PithosContainerNode *containerNode in accountNode.children) {
450 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
451 if (![containersNodeChildren containsObject:containerNode])
452 [containersNodeChildren insertObject:containerNode atIndex:0];
453 containerPithosFound = YES;
454 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
455 NSUInteger insertIndex = 1;
456 if (!containerPithosFound)
458 if (![containersNodeChildren containsObject:containerNode])
459 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
460 containerTrashFound = YES;
461 } else if (![containersNodeChildren containsObject:containerNode]) {
462 [containersNodeChildren addObject:containerNode];
465 BOOL refreshAccountNode = NO;
466 if (!containerPithosFound) {
467 // Create pithos node
468 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
469 containerName:@"pithos"];
470 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
472 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
473 if ([containerRequest error]) {
474 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
476 refreshAccountNode = YES;
479 if (!containerTrashFound) {
481 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
482 containerName:@"trash"];
483 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
485 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
486 if ([containerRequest error]) {
487 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
489 refreshAccountNode = YES;
493 if (refreshAccountNode)
494 [accountNode refresh];
496 [outlineView reloadData];
498 // Expand the folder outline view
499 [outlineView expandItem:nil expandChildren:YES];
501 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
502 rootNode = [containersNodeChildren objectAtIndex:0];
503 [browser loadColumnZero];
510 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
517 - (IBAction)forceRefresh:(id)sender {
521 [accountNode forceRefresh];
522 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
523 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
524 node.forcedRefresh = YES;
525 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
527 [browser validateVisibleColumns];
530 - (IBAction)refresh:(id)sender {
533 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
534 [self forceRefresh:sender];
537 [accountNode refresh];
538 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
539 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
541 [browser validateVisibleColumns];
546 #pragma mark NSBrowserDelegate
548 - (id)rootItemForBrowser:(NSBrowser *)browser {
552 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
553 PithosNode *node = (PithosNode *)item;
554 return node.children.count;
557 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
558 PithosNode *node = (PithosNode *)item;
559 return [node.children objectAtIndex:index];
562 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
563 PithosNode *node = (PithosNode *)item;
564 return node.isLeafItem;
567 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
568 PithosNode *node = (PithosNode *)item;
572 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
573 if (sharedPreviewController == nil)
574 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
575 return sharedPreviewController;
578 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
579 // if (!forUserResize) {
580 // id item = [browser parentForItemsInColumn:columnIndex];
581 // if ([self browser:browser isLeafItem:item]) {
582 // suggestedWidth = 200;
585 // return suggestedWidth;
588 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
594 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
595 PithosNode *node = (PithosNode *)item;
596 if (node.shared || node.sharingAccount ||
597 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
603 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
605 PithosNode *node = (PithosNode *)item;
606 NSString *newName = (NSString *)object;
607 NSUInteger newNameLength = [newName length];
608 NSRange firstSlashRange = [newName rangeOfString:@"/"];
609 if ((newNameLength == 0) ||
610 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
611 ([newName isEqualToString:node.displayName])) {
614 if (([node class] == [PithosObjectNode class]) ||
615 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
616 // Operation: Rename (move) an object or subdir/ node
617 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
619 if (operation.isCancelled)
621 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
622 if ([newName hasSuffix:@"/"])
623 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
624 NSError *error = nil;
626 if ([PithosUtilities objectExistsAtPithos:pithos
627 containerName:node.pithosContainer.name
628 objectName:destinationObjectName
630 isDirectory:&isDirectory
631 sharingAccount:nil]) {
632 dispatch_async(dispatch_get_main_queue(), ^{
633 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
634 [alert setMessageText:@"Name Taken"];
635 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
636 [alert addButtonWithTitle:@"OK"];
643 if (operation.isCancelled)
645 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
646 containerName:node.pithosContainer.name
647 objectName:node.pithosObject.name
648 destinationContainerName:node.pithosContainer.name
649 destinationObjectName:destinationObjectName
651 if (!operation.isCancelled && objectRequest) {
652 objectRequest.delegate = self;
653 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
654 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
655 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
656 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
657 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
658 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
659 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
660 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
661 message:messagePrefix];
662 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
663 [NSDictionary dictionaryWithObjectsAndKeys:
664 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
665 [NSNumber numberWithBool:YES], @"refresh",
666 activity, @"activity",
667 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
668 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
669 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
670 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
671 [NSNumber numberWithUnsignedInteger:10], @"retries",
672 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
673 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
674 moveNetworkQueue, @"networkQueue",
675 @"move", @"operationType",
677 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
681 [moveQueue addOperation:operation];
682 } else if ([node class] == [PithosSubdirNode class]) {
683 if (firstSlashRange.length == 1)
685 // Operation: Rename (move) a subdir node and its descendants
686 // The resulting ASIPithosObjectRequests are chained through dependencies
687 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
689 if (operation.isCancelled)
691 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
692 NSError *error = nil;
694 if ([PithosUtilities objectExistsAtPithos:pithos
695 containerName:node.pithosContainer.name
696 objectName:destinationObjectName
698 isDirectory:&isDirectory
699 sharingAccount:nil]) {
700 dispatch_async(dispatch_get_main_queue(), ^{
701 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
702 [alert setMessageText:@"Name Taken"];
703 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
704 [alert addButtonWithTitle:@"OK"];
711 if (operation.isCancelled)
713 if (node.pithosObject.subdir)
714 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
715 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
716 containerName:node.pithosContainer.name
717 objectName:node.pithosObject.name
718 destinationContainerName:node.pithosContainer.name
719 destinationObjectName:destinationObjectName
721 if (!operation.isCancelled && objectRequests) {
722 ASIPithosObjectRequest *previousObjectRequest = nil;
723 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
724 if (operation.isCancelled)
726 objectRequest.delegate = self;
727 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
728 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
729 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
730 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
731 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
732 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
733 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
734 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
735 message:messagePrefix];
736 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
737 [NSDictionary dictionaryWithObjectsAndKeys:
738 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
739 [NSNumber numberWithBool:YES], @"refresh",
740 activity, @"activity",
741 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
742 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
743 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
744 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
745 [NSNumber numberWithUnsignedInteger:10], @"retries",
746 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
747 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
748 moveNetworkQueue, @"networkQueue",
749 @"move", @"operationType",
751 if (previousObjectRequest)
752 [objectRequest addDependency:previousObjectRequest];
753 previousObjectRequest = objectRequest;
754 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
759 [moveQueue addOperation:operation];
763 #pragma mark Drag and Drop source
765 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
766 withEvent:(NSEvent *)event {
767 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
768 __block BOOL result = YES;
769 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
770 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
771 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
779 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
780 toPasteboard:(NSPasteboard *)pasteboard {
781 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
782 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
783 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
784 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
785 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
786 [propertyList addObject:[node.pithosObject.name pathExtension]];
787 [nodes addObject:node];
790 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
791 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
792 self.draggedNodes = nodes;
793 self.draggedParentNode = [browser parentForItemsInColumn:column];
797 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
798 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
799 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
800 for (PithosNode *node in draggedNodes) {
801 [names addObject:node.displayName];
802 // If the node is a subdir ask if the whole tree should be downloaded
803 if ([node class] == [PithosSubdirNode class]) {
804 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
805 [alert setMessageText:@"Download directory"];
806 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
807 [alert addButtonWithTitle:@"OK"];
808 [alert addButtonWithTitle:@"Cancel"];
809 NSInteger choice = [alert runModal];
810 if (choice == NSAlertFirstButtonReturn)
811 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
813 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
819 #pragma mark Drag and Drop destination
821 - (NSDragOperation)browser:aBrowser
822 validateDrop:(id<NSDraggingInfo>)info
823 proposedRow:(NSInteger *)row
824 column:(NSInteger *)column
825 dropOperation:(NSBrowserDropOperation *)dropOperation {
826 NSDragOperation result = NSDragOperationNone;
827 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
828 // For a drop above, the drop is redirected to the parent item
829 if (*dropOperation == NSBrowserDropAbove)
831 // Only allow dropping in folders
833 PithosNode *dropNode;
835 // Check if the node is not a folder and if so redirect to the parent item
836 dropNode = [browser itemAtRow:*row inColumn:*column];
837 if ([dropNode class] == [PithosObjectNode class])
841 dropNode = [browser parentForItemsInColumn:*column];
843 if (!dropNode.shared &&
844 (!dropNode.sharingAccount ||
845 ([dropNode class] == [PithosSubdirNode class]) ||
846 ([dropNode class] == [PithosContainerNode class]))) {
847 *dropOperation = NSBrowserDropOn;
848 result = NSDragOperationCopy;
851 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
852 // For a drop above, the drop is redirected to the parent item
853 if (*dropOperation == NSBrowserDropAbove)
855 // Only allow dropping in folders
857 PithosNode *dropNode;
859 // Check if the node is not a folder and if so redirect to the parent item
860 dropNode = [browser itemAtRow:*row inColumn:*column];
861 if ([dropNode class] == [PithosObjectNode class])
865 dropNode = [browser parentForItemsInColumn:*column];
867 if (!dropNode.shared && !dropNode.sharingAccount) {
868 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
869 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
870 if ((([dropNode class] == [PithosContainerNode class]) ||
871 dropNode.pithosObject.subdir ||
872 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
873 ![dropNode isEqualTo:draggedParentNode]) {
874 // ![dropNode isEqualTo:draggedParentNode] &&
875 // ![draggedNodes containsObject:dropNode]) {
876 result = NSDragOperationMove;
878 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
879 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
880 if (([dropNode class] == [PithosContainerNode class]) ||
881 dropNode.pithosObject.subdir ||
882 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
883 result = NSDragOperationCopy;
892 - (BOOL)browser:(NSBrowser *)aBrowser
893 acceptDrop:(id<NSDraggingInfo>)info
895 column:(NSInteger)column
896 dropOperation:(NSBrowserDropOperation)dropOperation {
897 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
898 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
899 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
900 if ((column != -1) && (filenames != nil)) {
903 node = [browser itemAtRow:row inColumn:column];
905 node = [browser parentForItemsInColumn:column];
906 DLog(@"drag in node: %@", node.url);
907 return [self uploadFiles:filenames toNode:node];
909 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
910 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
911 if ((column != -1) && (draggedNodes != nil)) {
914 node = [browser itemAtRow:row inColumn:column];
916 node = [browser parentForItemsInColumn:column];
917 DLog(@"drag local node: %@", node.url);
918 if ([info draggingSourceOperationMask] & NSDragOperationMove)
919 return [self moveNodes:draggedNodes toNode:node];
920 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
921 return [self copyNodes:draggedNodes toNode:node];
928 #pragma mark NSBrowser Actions
930 - (void)browserDoubleAction:(id)sender {
931 NSInteger column = [browser clickedColumn];
932 NSInteger row = [browser clickedRow];
933 if ((column == -1) || (row == -1))
935 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
936 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
937 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
938 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
939 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
940 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
943 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
945 NSMenuItem *menuItem = [[[NSMenuItem alloc] init] autorelease];
946 menuItem.representedObject = menuNodes;
947 [self menuDownload:menuItem];
951 #pragma mark Drag and Drop methods
953 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
954 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
955 if ([node class] == [PithosSubdirNode class]) {
956 // XXX newFilename and version are ignored in the case of a subdir node for now
957 // Operation: Download a subdir node and its descendants
958 // The resulting ASIPithosObjectRequests are chained through dependencies
959 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
961 if (operation.isCancelled)
963 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
964 containerName:node.pithosContainer.name
965 objectName:node.pithosObject.name
967 checkIfExists:checkIfExists
968 sharingAccount:node.sharingAccount];
969 if (!operation.isCancelled && objectRequests) {
970 ASIPithosObjectRequest *previousObjectRequest = nil;
971 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
972 if (operation.isCancelled)
974 objectRequest.delegate = self;
975 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
976 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
977 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
978 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
979 message:[messagePrefix stringByAppendingString:@" (0%)"]
980 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
982 dispatch_async(dispatch_get_main_queue(), ^{
983 [activityFacility updateActivity:activity withMessage:activity.message];
985 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
986 [NSDictionary dictionaryWithObjectsAndKeys:
987 activity, @"activity",
988 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
989 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
990 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
991 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
992 [NSNumber numberWithUnsignedInteger:10], @"retries",
993 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
994 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
995 downloadNetworkQueue, @"networkQueue",
996 @"download", @"operationType",
998 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
999 [activityFacility updateActivity:activity
1000 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1001 totalBytes:activity.totalBytes
1002 currentBytes:(activity.currentBytes + size)];
1004 if (previousObjectRequest)
1005 [objectRequest addDependency:previousObjectRequest];
1006 previousObjectRequest = objectRequest;
1007 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1012 [downloadQueue addOperation:operation];
1013 } else if ([node class] == [PithosObjectNode class]) {
1014 // Operation: Download an object node
1015 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1017 if (operation.isCancelled)
1019 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
1020 containerName:node.pithosContainer.name
1021 objectName:node.pithosObject.name
1024 withNewFileName:newFileName
1025 checkIfExists:checkIfExists
1026 sharingAccount:node.sharingAccount];
1027 if (!operation.isCancelled && objectRequest) {
1028 objectRequest.delegate = self;
1029 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1030 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1031 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1032 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1033 message:[messagePrefix stringByAppendingString:@" (0%)"]
1034 totalBytes:node.pithosObject.bytes
1036 dispatch_async(dispatch_get_main_queue(), ^{
1037 [activityFacility updateActivity:activity withMessage:activity.message];
1039 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1040 [NSDictionary dictionaryWithObjectsAndKeys:
1041 activity, @"activity",
1042 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1043 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1044 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1045 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1046 [NSNumber numberWithUnsignedInteger:10], @"retries",
1047 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1048 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1049 downloadNetworkQueue, @"networkQueue",
1050 @"download", @"operationType",
1052 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1053 [activityFacility updateActivity:activity
1054 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1055 totalBytes:activity.totalBytes
1056 currentBytes:(activity.currentBytes + size)];
1058 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1062 [downloadQueue addOperation:operation];
1066 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1067 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1069 NSFileManager *fileManager = [NSFileManager defaultManager];
1070 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1071 NSString *objectNamePrefix;
1072 if ([destinationNode class] == [PithosSubdirNode class])
1073 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1075 objectNamePrefix = [NSString string];
1076 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1077 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1078 containerName:containerName];
1079 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1081 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1082 if ([containerRequest error]) {
1083 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1085 } else if (containerRequest.responseStatusCode != 204) {
1086 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1089 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1090 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1092 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1093 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1095 for (NSString *filePath in filenames) {
1097 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1100 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1101 precomposedStringWithCanonicalMapping];
1102 // Operation: Upload a local file
1103 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1105 if (operation.isCancelled)
1107 NSError *error = nil;
1108 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1109 if (contentType == nil)
1110 contentType = @"application/octet-stream";
1113 DLog(@"contentType detection error: %@", error);
1115 NSArray *hashes = nil;
1116 if (operation.isCancelled)
1118 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1119 containerName:containerName
1120 objectName:objectName
1121 contentType:contentType
1127 sharingAccount:destinationNode.sharingAccount];
1128 if (!operation.isCancelled && objectRequest) {
1129 objectRequest.delegate = self;
1130 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1131 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1132 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1133 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1134 message:[messagePrefix stringByAppendingString:@" (0%)"]
1135 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1137 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1138 [NSDictionary dictionaryWithObjectsAndKeys:
1139 containerName, @"containerName",
1140 objectName, @"objectName",
1141 contentType, @"contentType",
1142 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1143 blockHash, @"blockHash",
1144 filePath, @"filePath",
1146 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1147 [NSNumber numberWithBool:YES], @"refresh",
1148 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1149 activity, @"activity",
1150 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1151 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1152 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1153 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1154 [NSNumber numberWithUnsignedInteger:10], @"retries",
1155 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1156 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1157 uploadNetworkQueue, @"networkQueue",
1158 @"upload", @"operationType",
1160 if (destinationNode.sharingAccount)
1161 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1162 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1166 [uploadQueue addOperation:operation];
1168 // Upload directory, confirm first
1169 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1170 [alert setMessageText:@"Upload directory"];
1171 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1172 [alert addButtonWithTitle:@"OK"];
1173 [alert addButtonWithTitle:@"Cancel"];
1174 NSInteger choice = [alert runModal];
1175 if (choice == NSAlertFirstButtonReturn) {
1176 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1177 precomposedStringWithCanonicalMapping];
1178 // Operation: Upload a local directory and its descendants
1179 // The resulting ASIPithosObjectRequests are chained through dependencies
1180 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1182 if (operation.isCancelled)
1184 NSMutableArray *objectNames = nil;
1185 NSMutableArray *contentTypes = nil;
1186 NSMutableArray *filePaths = nil;
1187 NSMutableArray *hashesArrays = nil;
1188 NSMutableArray *directoryObjectRequests = nil;
1189 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1190 containerName:containerName
1191 objectName:objectName
1194 forDirectory:filePath
1196 objectNames:&objectNames
1197 contentTypes:&contentTypes
1198 filePaths:&filePaths
1199 hashesArrays:&hashesArrays
1200 directoryObjectRequests:&directoryObjectRequests
1201 sharingAccount:destinationNode.sharingAccount];
1202 if (operation.isCancelled)
1204 ASIPithosObjectRequest *previousObjectRequest = nil;
1205 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1206 if (operation.isCancelled)
1208 objectRequest.delegate = self;
1209 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1210 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1211 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1212 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1213 message:messagePrefix];
1214 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1215 [NSDictionary dictionaryWithObjectsAndKeys:
1216 [NSNumber numberWithBool:YES], @"refresh",
1217 activity, @"activity",
1218 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1219 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1220 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1221 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1222 [NSNumber numberWithUnsignedInteger:10], @"retries",
1223 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1224 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1225 uploadNetworkQueue, @"networkQueue",
1226 @"upload", @"operationType",
1228 if (previousObjectRequest)
1229 [objectRequest addDependency:previousObjectRequest];
1230 previousObjectRequest = objectRequest;
1231 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1233 if (!operation.isCancelled && objectRequests) {
1234 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1235 if (operation.isCancelled)
1237 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1238 objectRequest.delegate = self;
1239 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1240 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1241 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1242 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1243 message:[messagePrefix stringByAppendingString:@" (0%)"]
1244 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1246 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1247 [NSDictionary dictionaryWithObjectsAndKeys:
1248 containerName, @"containerName",
1249 [objectNames objectAtIndex:i], @"objectName",
1250 [contentTypes objectAtIndex:i], @"contentType",
1251 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1252 blockHash, @"blockHash",
1253 [filePaths objectAtIndex:i], @"filePath",
1254 [hashesArrays objectAtIndex:i], @"hashes",
1255 [NSNumber numberWithBool:YES], @"refresh",
1256 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1257 activity, @"activity",
1258 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1259 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1260 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1261 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1262 [NSNumber numberWithUnsignedInteger:10], @"retries",
1263 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1264 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1265 uploadNetworkQueue, @"networkQueue",
1266 @"upload", @"operationType",
1268 if (destinationNode.sharingAccount)
1269 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1270 if (previousObjectRequest)
1271 [objectRequest addDependency:previousObjectRequest];
1272 previousObjectRequest = objectRequest;
1273 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1278 [uploadQueue addOperation:operation];
1286 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1287 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1288 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1290 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1291 NSString *objectNamePrefix;
1292 if ([destinationNode class] == [PithosSubdirNode class])
1293 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1295 objectNamePrefix = [NSString string];
1297 for (PithosNode *node in nodes) {
1298 if (([node class] == [PithosObjectNode class]) ||
1299 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1300 // Operation: Move an object or subdir/ node
1301 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1303 if (operation.isCancelled)
1305 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1306 if ([node.pithosObject.name hasSuffix:@"/"])
1307 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1308 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1309 containerName:node.pithosContainer.name
1310 objectName:node.pithosObject.name
1311 destinationContainerName:containerName
1312 destinationObjectName:destinationObjectName
1314 if (!operation.isCancelled && objectRequest) {
1315 objectRequest.delegate = self;
1316 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1317 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1318 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1319 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1320 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1321 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1322 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1323 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1324 message:messagePrefix];
1325 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1326 [NSDictionary dictionaryWithObjectsAndKeys:
1327 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1328 activity, @"activity",
1329 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1330 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1331 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1332 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1333 [NSNumber numberWithUnsignedInteger:10], @"retries",
1334 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1335 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1336 moveNetworkQueue, @"networkQueue",
1337 @"move", @"operationType",
1339 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1343 [moveQueue addOperation:operation];
1344 } else if ([node class] == [PithosSubdirNode class]) {
1345 // Operation: Move a subdir node and its descendants
1346 // The resulting ASIPithosObjectRequests are chained through dependencies
1347 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1349 if (operation.isCancelled)
1351 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1352 if (node.pithosObject.subdir)
1353 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1354 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1355 containerName:node.pithosContainer.name
1356 objectName:node.pithosObject.name
1357 destinationContainerName:containerName
1358 destinationObjectName:destinationObjectName
1360 if (!operation.isCancelled && objectRequests) {
1361 ASIPithosObjectRequest *previousObjectRequest = nil;
1362 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1363 if (operation.isCancelled)
1365 objectRequest.delegate = self;
1366 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1367 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1368 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1369 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1370 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1371 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1372 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1373 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1374 message:messagePrefix];
1375 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1376 [NSDictionary dictionaryWithObjectsAndKeys:
1377 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1378 [NSNumber numberWithBool:YES], @"refresh",
1379 activity, @"activity",
1380 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1381 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1382 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1383 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1384 [NSNumber numberWithUnsignedInteger:10], @"retries",
1385 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1386 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1387 moveNetworkQueue, @"networkQueue",
1388 @"move", @"operationType",
1390 if (previousObjectRequest)
1391 [objectRequest addDependency:previousObjectRequest];
1392 previousObjectRequest = objectRequest;
1393 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1398 [moveQueue addOperation:operation];
1404 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1405 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1406 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1408 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1409 NSString *objectNamePrefix;
1410 if ([destinationNode class] == [PithosSubdirNode class])
1411 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1413 objectNamePrefix = [NSString string];
1415 for (PithosNode *node in nodes) {
1416 if (([node class] == [PithosObjectNode class]) ||
1417 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1418 // Operation: Copy an object or subdir/ node
1419 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1421 if (operation.isCancelled)
1423 NSString *destinationObjectName;
1424 if (![destinationNode isEqualTo:node.parent]) {
1425 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1426 if ([node.pithosObject.name hasSuffix:@"/"])
1427 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1429 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1430 containerName:containerName
1431 objectName:node.pithosObject.name];
1433 if (operation.isCancelled)
1435 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos
1436 containerName:node.pithosContainer.name
1437 objectName:node.pithosObject.name
1438 destinationContainerName:containerName
1439 destinationObjectName:destinationObjectName
1441 sharingAccount:node.sharingAccount];
1442 if (!operation.isCancelled && objectRequest) {
1443 objectRequest.delegate = self;
1444 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1445 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1446 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1447 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1448 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1449 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1450 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1451 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1452 message:messagePrefix];
1453 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1454 [NSDictionary dictionaryWithObjectsAndKeys:
1455 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1456 activity, @"activity",
1457 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1458 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1459 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1460 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1461 [NSNumber numberWithUnsignedInteger:10], @"retries",
1462 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1463 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1464 copyNetworkQueue, @"networkQueue",
1465 @"copy", @"operationType",
1467 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1471 [copyQueue addOperation:operation];
1472 } else if ([node class] == [PithosSubdirNode class]) {
1473 // Operation: Copy a subdir node and its descendants
1474 // The resulting ASIPithosObjectRequests are chained through dependencies
1475 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1477 if (operation.isCancelled)
1479 NSString *destinationObjectName;
1480 if (![destinationNode isEqualTo:node.parent]) {
1481 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1482 if (node.pithosObject.subdir)
1483 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1485 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1486 containerName:containerName
1487 subdirName:node.pithosObject.name];
1489 if (operation.isCancelled)
1491 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos
1492 containerName:node.pithosContainer.name
1493 objectName:node.pithosObject.name
1494 destinationContainerName:containerName
1495 destinationObjectName:destinationObjectName
1497 sharingAccount:node.sharingAccount];
1498 if (!operation.isCancelled && objectRequests) {
1499 ASIPithosObjectRequest *previousObjectRequest = nil;
1500 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1501 if (operation.isCancelled)
1503 objectRequest.delegate = self;
1504 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1505 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1506 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1507 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1508 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1509 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1510 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1511 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1512 message:messagePrefix];
1513 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1514 [NSDictionary dictionaryWithObjectsAndKeys:
1515 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1516 activity, @"activity",
1517 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1518 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1519 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1520 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1521 [NSNumber numberWithUnsignedInteger:10], @"retries",
1522 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1523 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1524 copyNetworkQueue, @"networkQueue",
1525 @"copy", @"operationType",
1527 if (previousObjectRequest)
1528 [objectRequest addDependency:previousObjectRequest];
1529 previousObjectRequest = objectRequest;
1530 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1535 [copyQueue addOperation:operation];
1542 #pragma mark ASIHTTPRequestDelegate
1544 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1545 NSOperationQueue *callbackQueue;
1546 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1547 if ([operationType isEqualToString:@"move"])
1548 callbackQueue = moveCallbackQueue;
1549 else if ([operationType isEqualToString:@"copy"])
1550 callbackQueue = copyCallbackQueue;
1551 else if ([operationType isEqualToString:@"delete"])
1552 callbackQueue = deleteCallbackQueue;
1553 else if ([operationType isEqualToString:@"upload"])
1554 callbackQueue = uploadCallbackQueue;
1555 else if ([operationType isEqualToString:@"download"])
1556 callbackQueue = downloadCallbackQueue;
1558 dispatch_async(dispatch_get_main_queue(), ^{
1559 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1560 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1564 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1565 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1566 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1567 object:request] autorelease];
1568 operation.completionBlock = ^{
1570 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1571 dispatch_async(dispatch_get_main_queue(), ^{
1572 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1573 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1578 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1579 [callbackQueue addOperation:operation];
1582 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1583 if (request.isCancelled) {
1584 // Request has been cancelled
1585 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1586 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1587 withObject:request];
1589 NSOperationQueue *callbackQueue;
1590 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1591 if ([operationType isEqualToString:@"move"])
1592 callbackQueue = moveCallbackQueue;
1593 else if ([operationType isEqualToString:@"copy"])
1594 callbackQueue = copyCallbackQueue;
1595 else if ([operationType isEqualToString:@"delete"])
1596 callbackQueue = deleteCallbackQueue;
1597 else if ([operationType isEqualToString:@"upload"])
1598 callbackQueue = uploadCallbackQueue;
1599 else if ([operationType isEqualToString:@"download"])
1600 callbackQueue = downloadCallbackQueue;
1602 dispatch_async(dispatch_get_main_queue(), ^{
1603 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1604 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1608 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1609 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1610 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1611 object:request] autorelease];
1612 operation.completionBlock = ^{
1614 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1615 dispatch_async(dispatch_get_main_queue(), ^{
1616 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1617 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1622 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1623 [callbackQueue addOperation:operation];
1627 - (void)requestFailed:(ASIPithosRequest *)request {
1629 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1630 DLog(@"Request failed: %@", request.url);
1631 if (operation.isCancelled)
1633 if (request.isCancelled) {
1634 dispatch_async(dispatch_get_main_queue(), ^{
1635 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1636 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1640 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1642 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1643 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1644 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1645 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1647 dispatch_async(dispatch_get_main_queue(), ^{
1648 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1649 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1651 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1652 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1654 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1659 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1661 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1662 DLog(@"Download finished: %@", objectRequest.url);
1663 if (operation.isCancelled) {
1664 [self requestFailed:objectRequest];
1665 } else if (objectRequest.responseStatusCode == 200) {
1666 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1667 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1668 NSUInteger totalBytes = activity.totalBytes;
1670 // XXX change contentLength to objectContentLength if it is fixed in the server
1671 if ([objectRequest contentLength] == 0) {
1672 // The check above was:
1673 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1674 // I checked for directory content types in order not to create a file in place of a directory,
1675 // but this callback method is not called in the case of a directory download.
1676 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1677 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1678 DLog(@"Downloaded 0 bytes");
1679 NSFileManager *fileManager = [NSFileManager defaultManager];
1680 if (![fileManager fileExistsAtPath:filePath]) {
1681 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1682 dispatch_async(dispatch_get_main_queue(), ^{
1683 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1684 [alert setMessageText:@"Create File Error"];
1685 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1686 [alert addButtonWithTitle:@"OK"];
1693 NSUInteger currentBytes = [objectRequest objectContentLength];
1694 if (currentBytes == 0)
1695 currentBytes = totalBytes;
1696 dispatch_async(dispatch_get_main_queue(), ^{
1697 [activityFacility endActivity:activity
1698 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1699 totalBytes:totalBytes
1700 currentBytes:currentBytes];
1703 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1704 [self requestFailed:objectRequest];
1709 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1711 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1712 DLog(@"Upload directory object finished: %@", objectRequest.url);
1713 if (operation.isCancelled) {
1714 [self requestFailed:objectRequest];
1715 } else if (objectRequest.responseStatusCode == 201) {
1716 DLog(@"Directory object created: %@", objectRequest.url);
1717 dispatch_async(dispatch_get_main_queue(), ^{
1718 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1719 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1720 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1721 [node forceRefresh];
1723 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1726 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1727 [self forceRefresh:self];
1728 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1729 [self refresh:self];
1732 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1733 [self requestFailed:objectRequest];
1738 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1740 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1741 DLog(@"Upload using hashmap finished: %@", objectRequest.url);
1742 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1743 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1744 NSUInteger totalBytes = activity.totalBytes;
1745 NSUInteger currentBytes = activity.currentBytes;
1746 if (operation.isCancelled) {
1747 [self requestFailed:objectRequest];
1748 } else if (objectRequest.responseStatusCode == 201) {
1749 DLog(@"Object created: %@", objectRequest.url);
1750 dispatch_async(dispatch_get_main_queue(), ^{
1751 [activityFacility endActivity:activity
1752 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1753 totalBytes:totalBytes
1754 currentBytes:totalBytes];
1755 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1756 [node forceRefresh];
1758 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1761 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1762 [self forceRefresh:self];
1763 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1764 [self refresh:self];
1766 } else if (objectRequest.responseStatusCode == 409) {
1767 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1768 if (iteration == 0) {
1769 DLog(@"Upload iteration limit reached: %@", objectRequest.url);
1770 dispatch_async(dispatch_get_main_queue(), ^{
1771 [activityFacility endActivity:activity
1772 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1773 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1774 [alert setMessageText:@"Upload Timeout"];
1775 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1776 [objectRequest.userInfo objectForKey:@"objectName"]]];
1777 [alert addButtonWithTitle:@"OK"];
1782 DLog(@"object is missing hashes: %@", objectRequest.url);
1783 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1784 withMissingHashes:[objectRequest hashes]];
1785 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1786 if (totalBytes >= [missingBlocks count]*blockSize)
1787 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1788 dispatch_async(dispatch_get_main_queue(), ^{
1789 [activityFacility updateActivity:activity
1790 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1791 totalBytes:totalBytes
1792 currentBytes:currentBytes];
1794 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1795 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1796 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1798 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1799 missingBlockIndex:missingBlockIndex
1800 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1801 newContainerRequest.delegate = self;
1802 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1803 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1804 newContainerRequest.userInfo = objectRequest.userInfo;
1805 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1806 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1807 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1808 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1809 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1810 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1811 [activityFacility updateActivity:activity
1812 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1813 totalBytes:activity.totalBytes
1814 currentBytes:(activity.currentBytes + size)];
1816 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1818 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1819 [self requestFailed:objectRequest];
1824 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1826 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1827 DLog(@"Upload of missing block finished: %@", containerRequest.url);
1828 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1829 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1830 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1831 if (operation.isCancelled) {
1832 [self requestFailed:containerRequest];
1833 } else if (containerRequest.responseStatusCode == 202) {
1834 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1835 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1836 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1837 if (missingBlockIndex == NSNotFound) {
1838 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1839 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1840 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1841 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1842 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1844 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1845 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1848 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1849 newObjectRequest.delegate = self;
1850 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1851 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1852 newObjectRequest.userInfo = containerRequest.userInfo;
1853 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1854 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1855 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1856 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1857 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1859 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1860 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1861 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1862 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1863 missingBlockIndex:missingBlockIndex
1864 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1865 newContainerRequest.delegate = self;
1866 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1867 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1868 newContainerRequest.userInfo = containerRequest.userInfo;
1869 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1870 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1871 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1872 [activityFacility updateActivity:activity
1873 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1874 totalBytes:activity.totalBytes
1875 currentBytes:(activity.currentBytes + size)];
1877 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1880 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1881 [self requestFailed:containerRequest];
1886 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1888 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1889 DLog(@"Move object finished: %@", objectRequest.url);
1890 if (operation.isCancelled) {
1891 [self requestFailed:objectRequest];
1892 } else if (objectRequest.responseStatusCode == 201) {
1893 dispatch_async(dispatch_get_main_queue(), ^{
1894 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1895 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1896 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1897 [node forceRefresh];
1899 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1902 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1903 [self forceRefresh:self];
1904 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1905 [self refresh:self];
1908 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1909 [self requestFailed:objectRequest];
1914 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1916 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1917 DLog(@"Copy object finished: %@", objectRequest.url);
1918 if (operation.isCancelled) {
1919 [self requestFailed:objectRequest];
1920 } else if (objectRequest.responseStatusCode == 201) {
1921 dispatch_async(dispatch_get_main_queue(), ^{
1922 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1923 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1924 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1925 [node forceRefresh];
1927 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1930 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1931 [self forceRefresh:self];
1932 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1933 [self refresh:self];
1936 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1937 [self requestFailed:objectRequest];
1942 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1944 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1945 DLog(@"Delete object finished: %@", objectRequest.url);
1946 if (operation.isCancelled) {
1947 [self requestFailed:objectRequest];
1948 } else if (objectRequest.responseStatusCode == 204) {
1949 dispatch_async(dispatch_get_main_queue(), ^{
1950 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1951 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1952 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1953 [node forceRefresh];
1955 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1958 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1959 [self forceRefresh:self];
1960 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1961 [self refresh:self];
1964 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1965 [self requestFailed:objectRequest];
1971 #pragma mark NSSplitViewDelegate
1973 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1974 if (splitView == verticalSplitView)
1977 return ([horizontalSplitView bounds].size.height - 108);
1980 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1981 if (splitView == verticalSplitView)
1984 return ([horizontalSplitView bounds].size.height - 108);
1987 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1988 if (splitView == verticalSplitView) {
1989 if (proposedPosition < 120)
1991 else if (proposedPosition > 220)
1994 return proposedPosition;
1996 return ([horizontalSplitView bounds].size.height - 108);
2001 #pragma mark NSOutlineViewDataSource
2003 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2004 if (!browserInitialized)
2008 if (item == containersNode)
2009 return containersNodeChildren.count;
2010 if (item == sharedNode)
2015 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2016 if (!browserInitialized)
2019 return (!index ? containersNode : sharedNode);
2020 if (item == sharedNode)
2021 return (!index ? mySharedNode : othersSharedNode);
2022 return [containersNodeChildren objectAtIndex:index];
2025 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2026 if ((item == containersNode) || (item == sharedNode))
2031 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2032 PithosNode *node = (PithosNode *)item;
2036 #pragma mark Drag and Drop destination
2038 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2039 validateDrop:(id<NSDraggingInfo>)info
2040 proposedItem:(id)item
2041 proposedChildIndex:(NSInteger)index {
2042 NSDragOperation result = NSDragOperationNone;
2043 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2045 PithosNode *dropNode = (PithosNode *)item;
2046 if ([dropNode class] != [PithosContainerNode class])
2048 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2049 result = NSDragOperationCopy;
2050 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2051 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2052 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2053 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2054 if (![dropNode isEqualTo:draggedParentNode])
2055 result = NSDragOperationMove;
2056 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2057 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2058 result = NSDragOperationCopy;
2064 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2065 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2066 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2067 DLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2068 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2069 PithosNode *node = (PithosNode *)item;
2070 DLog(@"drag in node: %@", node.url);
2071 return [self uploadFiles:filenames toNode:node];
2073 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2074 DLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2075 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2076 PithosNode *node = (PithosNode *)item;
2077 DLog(@"drag local node: %@", node.url);
2078 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2079 ([info draggingSourceOperationMask] & NSDragOperationMove))
2080 return [self moveNodes:draggedNodes toNode:node];
2081 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2082 return [self copyNodes:draggedNodes toNode:node];
2089 #pragma mark NSOutlineViewDelegate
2091 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2092 if ((item == containersNode) || (item == sharedNode))
2097 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2098 if ((item == containersNode) || (item == sharedNode))
2103 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2104 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2107 [browser loadColumnZero];
2113 #pragma mark NSMenuDelegate
2115 - (void)menuNeedsUpdate:(NSMenu *)menu {
2116 [menu removeAllItems];
2117 NSMenuItem *menuItem;
2118 NSString *menuItemTitle;
2119 BOOL nodeContextMenu = NO;
2120 PithosNode *menuNode = nil;
2121 NSMutableArray *menuNodes;
2122 if (menu == browserMenu) {
2123 NSInteger column = [browser clickedColumn];
2124 NSInteger row = [browser clickedRow];
2125 if ((column == -1) || (row == -1)) {
2127 // General context menu
2128 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2129 if ([menuNodesIndexPaths count] == 0) {
2130 menuNode = [browser parentForItemsInColumn:0];
2131 } else if (([menuNodesIndexPaths count] != 1) ||
2132 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2133 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2135 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2138 menuNode = [browser parentForItemsInColumn:column];
2139 if ([menuNode class] == [PithosObjectNode class]) {
2140 // Node context menu
2141 menuNodes = [NSMutableArray arrayWithObject:menuNode];
2142 nodeContextMenu = YES;
2145 // General context menu
2148 // Node context menu
2149 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2150 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2151 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2152 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2153 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2154 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2157 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2159 nodeContextMenu = YES;
2161 } else if (menu == outlineViewMenu) {
2162 NSInteger row = [outlineView clickedRow];
2164 row = [outlineView selectedRow];
2167 menuNode = [outlineView itemAtRow:row];
2170 if (!nodeContextMenu) {
2171 // General context menu
2172 if (([menuNode class] == [PithosAccountNode class]) ||
2173 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2174 ([menuNode class] == [PithosEmptyNode class]))
2177 if (!menuNode.shared && !menuNode.sharingAccount) {
2178 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2179 [menuItem setRepresentedObject:menuNode];
2180 [menu addItem:menuItem];
2181 [menu addItem:[NSMenuItem separatorItem]];
2184 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2185 [menu addItem:menuItem];
2186 [menu addItem:[NSMenuItem separatorItem]];
2188 menuItem = [[[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2189 action:@selector(menuGetInfo:)
2190 keyEquivalent:@""] autorelease];
2191 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2192 [menu addItem:menuItem];
2194 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2195 (([menuNode class] == [PithosContainerNode class]) ||
2196 (([menuNode class] == [PithosSubdirNode class]) &&
2197 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2198 NSUInteger clipboardNodesCount = [clipboardNodes count];
2199 if (clipboardNodesCount == 0) {
2200 self.clipboardNodes = nil;
2201 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2202 if (clipboardNodesCount == 1)
2203 menuItemTitle = @"Paste Item";
2205 menuItemTitle = @"Paste Items";
2206 [menu addItem:[NSMenuItem separatorItem]];
2207 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2208 [menuItem setRepresentedObject:menuNode];
2209 [menu addItem:menuItem];
2213 // Node context menu
2214 NSUInteger menuNodesCount = [menuNodes count];
2215 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2217 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2218 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""] autorelease];
2219 [menuItem setRepresentedObject:menuNodes];
2220 [menu addItem:menuItem];
2221 [menu addItem:[NSMenuItem separatorItem]];
2223 // Move to Trash (pithos container only)
2225 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2226 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2227 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2228 action:@selector(menuMoveToTrash:)
2229 keyEquivalent:@""] autorelease];
2230 [menuItem setRepresentedObject:menuNodes];
2231 [menu addItem:menuItem];
2233 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2234 [menuItem setRepresentedObject:menuNodes];
2235 [menu addItem:menuItem];
2236 [menu addItem:[NSMenuItem separatorItem]];
2239 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2240 [menu addItem:menuItem];
2242 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2243 [menu addItem:[NSMenuItem separatorItem]];
2244 menuItem = [[[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2245 action:@selector(menuGetInfo:)
2246 keyEquivalent:@""] autorelease];
2247 [menuItem setRepresentedObject:menuNodes];
2248 [menu addItem:menuItem];
2250 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2251 [menu addItem:[NSMenuItem separatorItem]];
2254 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2255 if (menuNodesCount == 1)
2256 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2258 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2259 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2260 [menuItem setRepresentedObject:menuNodes];
2261 [menu addItem:menuItem];
2264 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2265 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2266 if (menuNodesCount == 1)
2267 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2269 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2270 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2271 [menuItem setRepresentedObject:menuNodes];
2272 [menu addItem:menuItem];
2275 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2276 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2277 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2278 NSUInteger clipboardNodesCount = [clipboardNodes count];
2279 if (clipboardNodesCount == 0) {
2280 self.clipboardNodes = nil;
2281 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2282 if (clipboardNodesCount == 1)
2283 menuItemTitle = @"Paste Item";
2285 menuItemTitle = @"Paste Items";
2286 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2287 [menuItem setRepresentedObject:firstMenuNode];
2288 [menu addItem:menuItem];
2295 #pragma mark NSMenuValidation
2297 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2298 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2299 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2300 if ([menuNodesIndexPaths count] == 0)
2303 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2304 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2305 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2306 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2307 ((menuItem.action == @selector(delete:)) &&
2308 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2309 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2312 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2313 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2314 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2316 menuItem.representedObject = menuNodes;
2317 } else if (menuItem.action == @selector(paste:)) {
2318 if (!clipboardNodes || ![clipboardNodes count])
2321 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2322 PithosNode *menuNode;
2323 if ([menuNodesIndexPaths count] == 0)
2324 menuNode = [browser parentForItemsInColumn:0];
2325 else if (([menuNodesIndexPaths count] != 1) ||
2326 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2327 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2329 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2331 if (menuNode.shared || menuNode.sharingAccount ||
2332 (([menuNode class] != [PithosContainerNode class]) &&
2333 (([menuNode class] != [PithosSubdirNode class]) ||
2334 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2335 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2338 menuItem.representedObject = menuNode;
2343 - (void)cut:(NSMenuItem *)sender {
2344 [self menuCut:sender];
2347 - (void)copy:(NSMenuItem *)sender {
2348 [self menuCopy:sender];
2351 - (void)paste:(NSMenuItem *)sender {
2352 [self menuPaste:sender];
2355 - (void)delete:(NSMenuItem *)sender {
2356 if (sender.tag == 0)
2357 [self menuMoveToTrash:sender];
2359 [self menuDelete:sender];
2363 #pragma mark Menu Actions
2365 - (void)menuNewFolder:(NSMenuItem *)sender {
2366 PithosNode *node = (PithosNode *)[sender representedObject];
2367 if ([node class] == [PithosContainerNode class]) {
2368 // Operation: Create (upload) a new root application/directory object
2369 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2371 if (operation.isCancelled)
2373 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2374 containerName:node.pithosContainer.name
2375 subdirName:@"untitled folder"];
2376 NSString *fileName = [safeObjectName lastPathComponent];
2377 if (operation.isCancelled)
2379 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2380 containerName:node.pithosContainer.name
2381 objectName:safeObjectName
2383 contentType:@"application/directory"
2385 contentDisposition:nil
2388 isPublic:ASIPithosObjectRequestPublicIgnore
2390 data:[NSData data]];
2391 objectRequest.delegate = self;
2392 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2393 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2394 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2395 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2396 message:messagePrefix];
2397 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2398 fileName, @"fileName",
2399 [NSArray arrayWithObject:node], @"refreshNodes",
2400 [NSNumber numberWithBool:YES], @"refresh",
2401 activity, @"activity",
2402 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2403 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2404 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2405 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2406 [NSNumber numberWithUnsignedInteger:10], @"retries",
2407 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2408 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2409 uploadNetworkQueue, @"networkQueue",
2410 @"upload", @"operationType",
2412 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2415 [uploadQueue addOperation:operation];
2416 } else if (([node class] == [PithosSubdirNode class]) &&
2417 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2418 // Operation: Create (upload) a new aplication/directory object
2419 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2421 if (operation.isCancelled)
2423 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2424 containerName:node.pithosContainer.name
2425 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2426 NSString *fileName = [safeObjectName lastPathComponent];
2427 if (operation.isCancelled)
2429 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2430 containerName:node.pithosContainer.name
2431 objectName:safeObjectName
2433 contentType:@"application/directory"
2435 contentDisposition:nil
2438 isPublic:ASIPithosObjectRequestPublicIgnore
2440 data:[NSData data]];
2441 objectRequest.delegate = self;
2442 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2443 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2444 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2445 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2446 message:messagePrefix];
2447 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2448 fileName, @"fileName",
2449 [NSArray arrayWithObject:node], @"refreshNodes",
2450 [NSNumber numberWithBool:YES], @"refresh",
2451 activity, @"activity",
2452 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2453 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2454 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2455 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2456 [NSNumber numberWithUnsignedInteger:10], @"retries",
2457 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2458 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2459 uploadNetworkQueue, @"networkQueue",
2460 @"upload", @"operationType",
2462 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2465 [uploadQueue addOperation:operation];
2469 - (void)menuGetInfo:(NSMenuItem *)sender {
2470 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2471 [node showPithosNodeInfo:sender];
2475 - (void)menuDownload:(NSMenuItem *)sender {
2476 NSArray *nodes = (NSArray *)[sender representedObject];
2477 PithosNode *firstNode = [nodes objectAtIndex:0];
2478 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2479 NSSavePanel *save = [NSSavePanel savePanel];
2480 save.nameFieldStringValue = firstNode.displayName;
2481 NSInteger result = [save runModal];
2482 if (result == NSOKButton) {
2483 NSString *destinationPath = save.URL.path;
2484 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2485 NSString *newFileName = [destinationPath lastPathComponent];
2486 if ([destinationPath hasSuffix:@"/"])
2487 newFileName = [newFileName stringByAppendingString:@"/"];
2488 if ([firstNode.displayName isEqualToString:newFileName])
2490 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2493 NSOpenPanel *open = [NSOpenPanel openPanel];
2494 open.canChooseFiles = NO;
2495 open.canChooseDirectories = YES;
2496 open.canCreateDirectories = YES;
2497 NSInteger result = [open runModal];
2498 if (result == NSOKButton) {
2499 NSString *directoryPath = open.URL.path;
2500 for (PithosNode *node in nodes) {
2501 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2507 - (void)menuDelete:(NSMenuItem *)sender {
2508 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2509 if (([node class] == [PithosObjectNode class]) ||
2510 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2511 // Operation: Delete an object or subdir/ node
2512 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2514 if (operation.isCancelled)
2516 NSString *fileName = [node.pithosObject.name lastPathComponent];
2517 if ([node.pithosObject.name hasSuffix:@"/"])
2518 fileName = [fileName stringByAppendingString:@"/"];
2519 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2520 containerName:node.pithosContainer.name
2521 objectName:node.pithosObject.name];
2522 if (operation.isCancelled)
2524 objectRequest.delegate = self;
2525 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2526 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2527 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2528 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2529 message:messagePrefix];
2530 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2531 fileName, @"fileName",
2532 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2533 activity, @"activity",
2534 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2535 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2536 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2537 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2538 [NSNumber numberWithUnsignedInteger:10], @"retries",
2539 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2540 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2541 deleteNetworkQueue, @"networkQueue",
2542 @"delete", @"operationType",
2544 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2547 [deleteQueue addOperation:operation];
2548 } else if ([node class] == [PithosSubdirNode class]) {
2549 // Operation: Delete a subdir node and its descendants
2550 // The resulting ASIPithosObjectRequests are chained through dependencies
2551 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2553 if (operation.isCancelled)
2555 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2556 containerName:node.pithosContainer.name
2557 objectName:node.pithosObject.name];
2558 if (!operation.isCancelled && objectRequests) {
2559 ASIPithosObjectRequest *previousObjectRequest = nil;
2560 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2561 if (operation.isCancelled)
2563 objectRequest.delegate = self;
2564 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2565 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2566 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2567 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2568 message:messagePrefix];
2569 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2570 [NSDictionary dictionaryWithObjectsAndKeys:
2571 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2572 activity, @"activity",
2573 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2574 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2575 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2576 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2577 [NSNumber numberWithUnsignedInteger:10], @"retries",
2578 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2579 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2580 deleteNetworkQueue, @"networkQueue",
2581 @"delete", @"operationType",
2583 if (previousObjectRequest)
2584 [objectRequest addDependency:previousObjectRequest];
2585 previousObjectRequest = objectRequest;
2586 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2591 [deleteQueue addOperation:operation];
2596 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2597 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2598 if (([node class] == [PithosObjectNode class]) ||
2599 (([node class] == [PithosSubdirNode class]) &&
2600 !node.pithosObject.subdir &&
2601 [node.pithosObject.name hasSuffix:@"/"])) {
2602 // Operation: Move to trash an object or subdir/ node
2603 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2605 if (operation.isCancelled)
2607 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2608 containerName:@"trash"
2609 objectName:node.pithosObject.name];
2610 if (!operation.isCancelled && safeObjectName) {
2611 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2612 containerName:node.pithosContainer.name
2613 objectName:node.pithosObject.name
2614 destinationContainerName:@"trash"
2615 destinationObjectName:safeObjectName
2617 if (!operation.isCancelled && objectRequest) {
2618 objectRequest.delegate = self;
2619 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2620 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2621 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2622 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2623 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2624 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2625 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2626 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2627 message:messagePrefix];
2628 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2629 [NSDictionary dictionaryWithObjectsAndKeys:
2630 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2631 activity, @"activity",
2632 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2633 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2634 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2635 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2636 [NSNumber numberWithUnsignedInteger:10], @"retries",
2637 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2638 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2639 moveNetworkQueue, @"networkQueue",
2640 @"move", @"operationType",
2642 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2647 [moveQueue addOperation:operation];
2648 } else if ([node class] == [PithosSubdirNode class]) {
2649 // Operation: Move to trash a subdir node and its descendants
2650 // The resulting ASIPithosObjectRequests are chained through dependencies
2651 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2653 if (operation.isCancelled)
2655 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2656 containerName:@"trash"
2657 subdirName:node.pithosObject.name];
2658 if (!operation.isCancelled && safeObjectName) {
2659 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2660 containerName:node.pithosContainer.name
2661 objectName:node.pithosObject.name
2662 destinationContainerName:@"trash"
2663 destinationObjectName:safeObjectName
2665 if (!operation.isCancelled && objectRequests) {
2666 ASIPithosObjectRequest *previousObjectRequest = nil;
2667 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2668 if (operation.isCancelled)
2670 objectRequest.delegate = self;
2671 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2672 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2673 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2674 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2675 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2676 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2677 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2678 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2679 message:messagePrefix];
2680 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2681 [NSDictionary dictionaryWithObjectsAndKeys:
2682 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2683 activity, @"activity",
2684 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2685 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2686 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2687 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2688 [NSNumber numberWithUnsignedInteger:10], @"retries",
2689 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2690 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2691 moveNetworkQueue, @"networkQueue",
2692 @"move", @"operationType",
2694 if (previousObjectRequest)
2695 [objectRequest addDependency:previousObjectRequest];
2696 previousObjectRequest = objectRequest;
2697 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2703 [moveQueue addOperation:operation];
2708 - (void)menuCut:(NSMenuItem *)sender {
2709 self.clipboardNodes = (NSArray *)[sender representedObject];
2710 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2711 self.clipboardCopy = NO;
2714 - (void)menuCopy:(NSMenuItem *)sender {
2715 self.clipboardNodes = (NSArray *)[sender representedObject];
2716 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2717 self.clipboardCopy = YES;
2720 - (void)menuPaste:(NSMenuItem *)sender {
2721 if (!clipboardNodes || ![clipboardNodes count])
2723 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2724 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2725 if (clipboardCopy) {
2726 [self copyNodes:localClipboardNodes toNode:dropNode];
2727 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2728 self.clipboardNodes = nil;
2729 self.clipboardParentNode = nil;
2730 [self moveNodes:localClipboardNodes toNode:dropNode];
2735 #pragma mark PithosActivityFacilityDelegate
2737 - (void)activityUpdate:(NSDictionary *)info {
2738 NSString *message = [info objectForKey:@"message"];
2739 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2740 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2741 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2742 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2743 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2744 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2745 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2746 if (runningActivitiesCount && totalBytes) {
2747 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2748 [activityProgressIndicator startAnimation:self];
2750 [activityProgressIndicator setDoubleValue:1.0];
2751 [activityProgressIndicator stopAnimation:self];
2755 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2756 [activityTextField setStringValue:message];