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];
148 moveNetworkQueue = [[ASINetworkQueue alloc] init];
149 moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
150 // moveNetworkQueue.maxConcurrentOperationCount = 1;
151 copyNetworkQueue = [[ASINetworkQueue alloc] init];
152 copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
153 // copyNetworkQueue.maxConcurrentOperationCount = 1;
154 deleteNetworkQueue = [[ASINetworkQueue alloc] init];
155 deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
156 // deleteNetworkQueue.maxConcurrentOperationCount = 1;
157 uploadNetworkQueue = [[ASINetworkQueue alloc] init];
158 uploadNetworkQueue.showAccurateProgress = YES;
159 uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
160 // uploadNetworkQueue.maxConcurrentOperationCount = 1;
161 downloadNetworkQueue = [[ASINetworkQueue alloc] init];
162 downloadNetworkQueue.showAccurateProgress = YES;
163 downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
164 // downloadNetworkQueue.maxConcurrentOperationCount = 1;
166 moveQueue = [[NSOperationQueue alloc] init];
167 [moveQueue setSuspended:YES];
168 moveQueue.name = @"gr.grnet.pithos.MoveQueue";
169 // moveQueue.maxConcurrentOperationCount = 1;
170 copyQueue = [[NSOperationQueue alloc] init];
171 [copyQueue setSuspended:YES];
172 copyQueue.name = @"gr.grnet.pithos.CopyQueue";
173 // copyQueue.maxConcurrentOperationCount = 1;
174 deleteQueue = [[NSOperationQueue alloc] init];
175 [deleteQueue setSuspended:YES];
176 deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
177 // deleteQueue.maxConcurrentOperationCount = 1;
178 uploadQueue = [[NSOperationQueue alloc] init];
179 [uploadQueue setSuspended:YES];
180 uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
181 // uploadQueue.maxConcurrentOperationCount = 1;
182 downloadQueue = [[NSOperationQueue alloc] init];
183 [downloadQueue setSuspended:YES];
184 downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
185 // downloadQueue.maxConcurrentOperationCount = 1;
187 moveCallbackQueue = [[NSOperationQueue alloc] init];
188 [moveCallbackQueue setSuspended:YES];
189 moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
190 // moveCallbackQueue.maxConcurrentOperationCount = 1;
191 copyCallbackQueue = [[NSOperationQueue alloc] init];
192 [copyCallbackQueue setSuspended:YES];
193 copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
194 // copyCallbackQueue.maxConcurrentOperationCount = 1;
195 deleteCallbackQueue = [[NSOperationQueue alloc] init];
196 [deleteCallbackQueue setSuspended:YES];
197 deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
198 // deleteCallbackQueue.maxConcurrentOperationCount = 1;
199 uploadCallbackQueue = [[NSOperationQueue alloc] init];
200 [uploadCallbackQueue setSuspended:YES];
201 uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
202 // uploadCallbackQueue.maxConcurrentOperationCount = 1;
203 downloadCallbackQueue = [[NSOperationQueue alloc] init];
204 [downloadCallbackQueue setSuspended:YES];
205 downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
206 // downloadCallbackQueue.maxConcurrentOperationCount = 1;
208 [activityProgressIndicator setUsesThreadedAnimation:YES];
209 [activityProgressIndicator setMinValue:0.0];
210 [activityProgressIndicator setMaxValue:1.0];
211 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
213 self.accountNode = [[[PithosAccountNode alloc] initWithPithos:pithos] autorelease];
214 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
215 containersNodeChildren = [[NSMutableArray alloc] init];
216 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
217 mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
218 mySharedNode.displayName = @"shared by me";
219 mySharedNode.shared = YES;
220 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
221 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
222 othersSharedNode.displayName = @"shared to me";
223 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
225 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
227 // Register for updates
228 // PithosAccountNode accountNode updates outlineView container nodes
229 [[NSNotificationCenter defaultCenter] addObserver:self
230 selector:@selector(pithosAccountNodeChildrenUpdated:)
231 name:@"PithosNodeChildrenUpdated"
233 // PithosNode updates browser nodes
234 [[NSNotificationCenter defaultCenter] addObserver:self
235 selector:@selector(pithosNodeChildrenUpdated:)
236 name:@"PithosNodeChildrenUpdated"
238 // Request for browser refresh
239 [[NSNotificationCenter defaultCenter] addObserver:self
240 selector:@selector(pithosBrowserRefreshNeeded:)
241 name:@"PithosBrowserRefreshNeeeded"
245 - (void)resetBrowser {
246 @synchronized(self) {
251 [refreshTimer invalidate];
252 [refreshTimer release];
254 [moveNetworkQueue reset];
255 [copyNetworkQueue reset];
256 [deleteNetworkQueue reset];
257 [uploadNetworkQueue reset];
258 [downloadNetworkQueue reset];
260 [moveQueue cancelAllOperations];
261 [moveQueue setSuspended:YES];
262 [copyQueue cancelAllOperations];
263 [copyQueue setSuspended:YES];
264 [deleteQueue cancelAllOperations];
265 [deleteQueue setSuspended:YES];
266 [uploadQueue cancelAllOperations];
267 [uploadQueue setSuspended:YES];
268 [downloadQueue cancelAllOperations];
269 [downloadQueue setSuspended:YES];
271 [moveCallbackQueue cancelAllOperations];
272 [moveCallbackQueue setSuspended:YES];
273 [copyCallbackQueue cancelAllOperations];
274 [copyCallbackQueue setSuspended:YES];
275 [deleteCallbackQueue cancelAllOperations];
276 [deleteCallbackQueue setSuspended:YES];
277 [uploadCallbackQueue cancelAllOperations];
278 [uploadCallbackQueue setSuspended:YES];
279 [downloadCallbackQueue cancelAllOperations];
280 [downloadCallbackQueue setSuspended:YES];
283 [browser loadColumnZero];
284 [containersNodeChildren removeAllObjects];
285 [outlineView reloadData];
286 // Expand the folder outline view
287 [outlineView expandItem:nil expandChildren:YES];
288 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
290 activityFacility.delegate = nil;
291 [activityProgressIndicator setDoubleValue:1.0];
292 [activityProgressIndicator stopAnimation:self];
294 @synchronized(self) {
299 - (void)startBrowser {
300 @synchronized(self) {
305 // In the improbable case of leftover operations
306 [moveNetworkQueue reset];
307 [copyNetworkQueue reset];
308 [deleteNetworkQueue reset];
309 [uploadNetworkQueue reset];
310 [downloadNetworkQueue reset];
311 [moveQueue cancelAllOperations];
312 [copyQueue cancelAllOperations];
313 [deleteQueue cancelAllOperations];
314 [uploadQueue cancelAllOperations];
315 [downloadQueue cancelAllOperations];
316 [moveCallbackQueue cancelAllOperations];
317 [copyCallbackQueue cancelAllOperations];
318 [deleteCallbackQueue cancelAllOperations];
319 [uploadCallbackQueue cancelAllOperations];
320 [downloadCallbackQueue cancelAllOperations];
322 [moveNetworkQueue go];
323 [copyNetworkQueue go];
324 [deleteNetworkQueue go];
325 [uploadNetworkQueue go];
326 [downloadNetworkQueue go];
327 [moveQueue setSuspended:NO];
328 [copyQueue setSuspended:NO];
329 [deleteQueue setSuspended:NO];
330 [uploadQueue setSuspended:NO];
331 [downloadQueue setSuspended:NO];
332 [moveCallbackQueue setSuspended:NO];
333 [copyCallbackQueue setSuspended:NO];
334 [deleteCallbackQueue setSuspended:NO];
335 [uploadCallbackQueue setSuspended:NO];
336 [downloadCallbackQueue setSuspended:NO];
338 accountNode.pithos = pithos;
339 [accountNode forceRefresh];
340 mySharedNode.pithos = pithos;
341 [mySharedNode forceRefresh];
342 othersSharedNode.pithos = pithos;
343 [othersSharedNode forceRefresh];
345 // [activityFacility reset];
346 activityFacility.delegate = self;
348 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
350 selector:@selector(forceRefresh:)
352 repeats:YES] retain];
353 @synchronized(self) {
358 - (BOOL)operationsPending {
359 return ([moveNetworkQueue operationCount] ||
360 [copyNetworkQueue operationCount] ||
361 [deleteNetworkQueue operationCount] ||
362 [uploadNetworkQueue operationCount] ||
363 [downloadNetworkQueue operationCount] ||
364 [moveQueue operationCount] ||
365 [copyQueue operationCount] ||
366 [deleteQueue operationCount] ||
367 [uploadQueue operationCount] ||
368 [downloadQueue operationCount] ||
369 [moveCallbackQueue operationCount] ||
370 [copyCallbackQueue operationCount] ||
371 [deleteCallbackQueue operationCount] ||
372 [uploadCallbackQueue operationCount] ||
373 [downloadCallbackQueue operationCount]);
377 [[NSNotificationCenter defaultCenter] removeObserver:self];
381 [deleteQueue release];
382 [uploadQueue release];
383 [downloadQueue release];
384 [moveNetworkQueue release];
385 [copyNetworkQueue release];
386 [deleteNetworkQueue release];
387 [uploadNetworkQueue release];
388 [downloadNetworkQueue release];
389 [clipboardParentNode release];
390 [clipboardNodes release];
391 [draggedParentNode release];
392 [draggedNodes release];
393 [sharedPreviewController release];
394 [othersSharedNode release];
395 [mySharedNode release];
396 [sharedNode release];
397 [containersNodeChildren release];
398 [containersNode release];
399 [accountNode release];
405 - (void)setPithos:(ASIPithos *)aPithos {
407 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
408 ![aPithos.authToken isEqualToString:pithos.authToken] ||
409 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
410 ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
413 pithos = [aPithos retain];
423 #pragma mark Observers
425 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
426 PithosNode *node = (PithosNode *)[notification object];
427 if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
429 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
430 NSInteger lastColumn = [browser lastColumn];
431 for (NSInteger column = lastColumn; column >= 0; column--) {
432 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
433 [browser reloadColumn:column];
439 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
440 BOOL containerPithosFound = NO;
441 BOOL containerTrashFound = NO;
442 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
443 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
444 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
445 [removedContainersNodeChildren addIndex:i];
447 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
448 for (PithosContainerNode *containerNode in accountNode.children) {
449 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
450 if (![containersNodeChildren containsObject:containerNode])
451 [containersNodeChildren insertObject:containerNode atIndex:0];
452 containerPithosFound = YES;
453 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
454 NSUInteger insertIndex = 1;
455 if (!containerPithosFound)
457 if (![containersNodeChildren containsObject:containerNode])
458 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
459 containerTrashFound = YES;
460 } else if (![containersNodeChildren containsObject:containerNode]) {
461 [containersNodeChildren addObject:containerNode];
464 BOOL refreshAccountNode = NO;
465 if (!containerPithosFound) {
466 // Create pithos node
467 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
468 containerName:@"pithos"];
469 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
471 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
472 if ([containerRequest error]) {
473 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
475 refreshAccountNode = YES;
478 if (!containerTrashFound) {
480 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
481 containerName:@"trash"];
482 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
484 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
485 if ([containerRequest error]) {
486 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
488 refreshAccountNode = YES;
492 if (refreshAccountNode)
493 [accountNode refresh];
495 [outlineView reloadData];
497 // Expand the folder outline view
498 [outlineView expandItem:nil expandChildren:YES];
500 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
501 rootNode = [containersNodeChildren objectAtIndex:0];
502 [browser loadColumnZero];
509 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
516 - (IBAction)forceRefresh:(id)sender {
520 [accountNode forceRefresh];
521 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
522 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
523 node.forcedRefresh = YES;
524 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
526 [browser validateVisibleColumns];
529 - (IBAction)refresh:(id)sender {
532 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
533 [self forceRefresh:sender];
536 [accountNode refresh];
537 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
538 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
540 [browser validateVisibleColumns];
545 #pragma mark NSBrowserDelegate
547 - (id)rootItemForBrowser:(NSBrowser *)browser {
551 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
552 PithosNode *node = (PithosNode *)item;
553 return node.children.count;
556 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
557 PithosNode *node = (PithosNode *)item;
558 return [node.children objectAtIndex:index];
561 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
562 PithosNode *node = (PithosNode *)item;
563 return node.isLeafItem;
566 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
567 PithosNode *node = (PithosNode *)item;
571 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
572 if (sharedPreviewController == nil)
573 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
574 return sharedPreviewController;
577 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
578 // if (!forUserResize) {
579 // id item = [browser parentForItemsInColumn:columnIndex];
580 // if ([self browser:browser isLeafItem:item]) {
581 // suggestedWidth = 200;
584 // return suggestedWidth;
587 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
593 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
594 PithosNode *node = (PithosNode *)item;
595 if (node.shared || node.sharingAccount ||
596 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
602 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
604 PithosNode *node = (PithosNode *)item;
605 NSString *newName = (NSString *)object;
606 NSUInteger newNameLength = [newName length];
607 NSRange firstSlashRange = [newName rangeOfString:@"/"];
608 if ((newNameLength == 0) ||
609 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
610 ([newName isEqualToString:node.displayName])) {
613 if (([node class] == [PithosObjectNode class]) ||
614 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
615 // Operation: Rename (move) an object or subdir/ node
616 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
617 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
618 if (operation.isCancelled) {
622 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
623 if ([newName hasSuffix:@"/"])
624 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
625 NSError *error = nil;
627 if ([PithosUtilities objectExistsAtPithos:pithos
628 containerName:node.pithosContainer.name
629 objectName:destinationObjectName
631 isDirectory:&isDirectory
632 sharingAccount:nil]) {
633 dispatch_async(dispatch_get_main_queue(), ^{
634 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
635 [alert setMessageText:@"Name Taken"];
636 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
637 [alert addButtonWithTitle:@"OK"];
646 if (operation.isCancelled) {
650 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
651 containerName:node.pithosContainer.name
652 objectName:node.pithosObject.name
653 destinationContainerName:node.pithosContainer.name
654 destinationObjectName:destinationObjectName
656 if (!operation.isCancelled && objectRequest) {
657 objectRequest.delegate = self;
658 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
659 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
660 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
661 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
662 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
663 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
664 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
665 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
666 message:messagePrefix];
667 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
668 [NSDictionary dictionaryWithObjectsAndKeys:
669 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
670 [NSNumber numberWithBool:YES], @"refresh",
671 activity, @"activity",
672 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
673 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
674 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
675 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
676 [NSNumber numberWithUnsignedInteger:10], @"retries",
677 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
678 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
679 moveNetworkQueue, @"networkQueue",
680 @"move", @"operationType",
682 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
686 [moveQueue addOperation:operation];
687 } else if ([node class] == [PithosSubdirNode class]) {
688 if (firstSlashRange.length == 1)
690 // Operation: Rename (move) a subdir node and its descendants
691 // The resulting ASIPithosObjectRequests are chained through dependencies
692 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
693 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
694 if (operation.isCancelled) {
698 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
699 NSError *error = nil;
701 if ([PithosUtilities objectExistsAtPithos:pithos
702 containerName:node.pithosContainer.name
703 objectName:destinationObjectName
705 isDirectory:&isDirectory
706 sharingAccount:nil]) {
707 dispatch_async(dispatch_get_main_queue(), ^{
708 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
709 [alert setMessageText:@"Name Taken"];
710 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
711 [alert addButtonWithTitle:@"OK"];
720 if (operation.isCancelled) {
724 if (node.pithosObject.subdir)
725 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
726 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
727 containerName:node.pithosContainer.name
728 objectName:node.pithosObject.name
729 destinationContainerName:node.pithosContainer.name
730 destinationObjectName:destinationObjectName
732 if (!operation.isCancelled && objectRequests) {
733 ASIPithosObjectRequest *previousObjectRequest = nil;
734 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
735 if (operation.isCancelled) {
739 objectRequest.delegate = self;
740 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
741 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
742 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
743 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
744 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
745 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
746 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
747 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
748 message:messagePrefix];
749 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
750 [NSDictionary dictionaryWithObjectsAndKeys:
751 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
752 [NSNumber numberWithBool:YES], @"refresh",
753 activity, @"activity",
754 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
755 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
756 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
757 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
758 [NSNumber numberWithUnsignedInteger:10], @"retries",
759 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
760 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
761 moveNetworkQueue, @"networkQueue",
762 @"move", @"operationType",
764 if (previousObjectRequest)
765 [objectRequest addDependency:previousObjectRequest];
766 previousObjectRequest = objectRequest;
767 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
772 [moveQueue addOperation:operation];
776 #pragma mark Drag and Drop source
778 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
779 withEvent:(NSEvent *)event {
780 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
781 __block BOOL result = YES;
782 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
783 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
784 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
792 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
793 toPasteboard:(NSPasteboard *)pasteboard {
794 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
795 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
796 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
797 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
798 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
799 [propertyList addObject:[node.pithosObject.name pathExtension]];
800 [nodes addObject:node];
803 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
804 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
805 self.draggedNodes = nodes;
806 self.draggedParentNode = [browser parentForItemsInColumn:column];
810 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
811 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
812 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
813 for (PithosNode *node in draggedNodes) {
814 [names addObject:node.displayName];
815 // If the node is a subdir ask if the whole tree should be downloaded
816 if ([node class] == [PithosSubdirNode class]) {
817 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
818 [alert setMessageText:@"Download directory"];
819 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
820 [alert addButtonWithTitle:@"OK"];
821 [alert addButtonWithTitle:@"Cancel"];
822 NSInteger choice = [alert runModal];
823 if (choice == NSAlertFirstButtonReturn)
824 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
826 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
832 #pragma mark Drag and Drop destination
834 - (NSDragOperation)browser:aBrowser
835 validateDrop:(id<NSDraggingInfo>)info
836 proposedRow:(NSInteger *)row
837 column:(NSInteger *)column
838 dropOperation:(NSBrowserDropOperation *)dropOperation {
839 NSDragOperation result = NSDragOperationNone;
840 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
841 // For a drop above, the drop is redirected to the parent item
842 if (*dropOperation == NSBrowserDropAbove)
844 // Only allow dropping in folders
846 PithosNode *dropNode;
848 // Check if the node is not a folder and if so redirect to the parent item
849 dropNode = [browser itemAtRow:*row inColumn:*column];
850 if ([dropNode class] == [PithosObjectNode class])
854 dropNode = [browser parentForItemsInColumn:*column];
856 if (!dropNode.shared &&
857 (!dropNode.sharingAccount ||
858 ([dropNode class] == [PithosSubdirNode class]) ||
859 ([dropNode class] == [PithosContainerNode class]))) {
860 *dropOperation = NSBrowserDropOn;
861 result = NSDragOperationCopy;
864 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
865 // For a drop above, the drop is redirected to the parent item
866 if (*dropOperation == NSBrowserDropAbove)
868 // Only allow dropping in folders
870 PithosNode *dropNode;
872 // Check if the node is not a folder and if so redirect to the parent item
873 dropNode = [browser itemAtRow:*row inColumn:*column];
874 if ([dropNode class] == [PithosObjectNode class])
878 dropNode = [browser parentForItemsInColumn:*column];
880 if (!dropNode.shared && !dropNode.sharingAccount) {
881 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
882 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
883 if ((([dropNode class] == [PithosContainerNode class]) ||
884 dropNode.pithosObject.subdir ||
885 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
886 ![dropNode isEqualTo:draggedParentNode]) {
887 // ![dropNode isEqualTo:draggedParentNode] &&
888 // ![draggedNodes containsObject:dropNode]) {
889 result = NSDragOperationMove;
891 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
892 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
893 if (([dropNode class] == [PithosContainerNode class]) ||
894 dropNode.pithosObject.subdir ||
895 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
896 result = NSDragOperationCopy;
905 - (BOOL)browser:(NSBrowser *)aBrowser
906 acceptDrop:(id<NSDraggingInfo>)info
908 column:(NSInteger)column
909 dropOperation:(NSBrowserDropOperation)dropOperation {
910 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
911 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
912 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
913 if ((column != -1) && (filenames != nil)) {
916 node = [browser itemAtRow:row inColumn:column];
918 node = [browser parentForItemsInColumn:column];
919 NSLog(@"drag in node: %@", node.url);
920 return [self uploadFiles:filenames toNode:node];
922 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
923 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
924 if ((column != -1) && (draggedNodes != nil)) {
927 node = [browser itemAtRow:row inColumn:column];
929 node = [browser parentForItemsInColumn:column];
930 NSLog(@"drag local node: %@", node.url);
931 if ([info draggingSourceOperationMask] & NSDragOperationMove)
932 return [self moveNodes:draggedNodes toNode:node];
933 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
934 return [self copyNodes:draggedNodes toNode:node];
941 #pragma mark Drag and Drop methods
943 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
944 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
945 if ([node class] == [PithosSubdirNode class]) {
946 // XXX newFilename and version are ignored in the case of a subdir node for now
947 // Operation: Download a subdir node and its descendants
948 // The resulting ASIPithosObjectRequests are chained through dependencies
949 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
950 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
951 if (operation.isCancelled) {
955 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
956 containerName:node.pithosContainer.name
957 objectName:node.pithosObject.name
959 checkIfExists:checkIfExists
960 sharingAccount:node.sharingAccount];
961 if (!operation.isCancelled && objectRequests) {
962 ASIPithosObjectRequest *previousObjectRequest = nil;
963 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
964 if (operation.isCancelled) {
968 objectRequest.delegate = self;
969 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
970 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
971 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
972 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
973 message:[messagePrefix stringByAppendingString:@" (0%)"]
974 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
976 dispatch_async(dispatch_get_main_queue(), ^{
977 [activityFacility updateActivity:activity withMessage:activity.message];
979 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
980 [NSDictionary dictionaryWithObjectsAndKeys:
981 activity, @"activity",
982 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
983 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
984 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
985 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
986 [NSNumber numberWithUnsignedInteger:10], @"retries",
987 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
988 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
989 downloadNetworkQueue, @"networkQueue",
990 @"download", @"operationType",
992 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
993 [activityFacility updateActivity:activity
994 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
995 totalBytes:activity.totalBytes
996 currentBytes:(activity.currentBytes + size)];
998 if (previousObjectRequest)
999 [objectRequest addDependency:previousObjectRequest];
1000 previousObjectRequest = objectRequest;
1001 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1006 [downloadQueue addOperation:operation];
1007 } else if ([node class] == [PithosObjectNode class]) {
1008 // Operation: Download an object node
1009 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1010 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1011 if (operation.isCancelled) {
1015 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
1016 containerName:node.pithosContainer.name
1017 objectName:node.pithosObject.name
1020 withNewFileName:newFileName
1021 checkIfExists:checkIfExists
1022 sharingAccount:node.sharingAccount];
1023 if (!operation.isCancelled && objectRequest) {
1024 objectRequest.delegate = self;
1025 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1026 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1027 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1028 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1029 message:[messagePrefix stringByAppendingString:@" (0%)"]
1030 totalBytes:node.pithosObject.bytes
1032 dispatch_async(dispatch_get_main_queue(), ^{
1033 [activityFacility updateActivity:activity withMessage:activity.message];
1035 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1036 [NSDictionary dictionaryWithObjectsAndKeys:
1037 activity, @"activity",
1038 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1039 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1040 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1041 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1042 [NSNumber numberWithUnsignedInteger:10], @"retries",
1043 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1044 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1045 downloadNetworkQueue, @"networkQueue",
1046 @"download", @"operationType",
1048 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1049 [activityFacility updateActivity:activity
1050 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1051 totalBytes:activity.totalBytes
1052 currentBytes:(activity.currentBytes + size)];
1054 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1058 [downloadQueue addOperation:operation];
1062 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1063 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1065 NSFileManager *fileManager = [NSFileManager defaultManager];
1066 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1067 NSString *objectNamePrefix;
1068 if ([destinationNode class] == [PithosSubdirNode class])
1069 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1071 objectNamePrefix = [NSString string];
1072 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1073 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1074 containerName:containerName];
1075 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1077 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1078 if ([containerRequest error]) {
1079 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1081 } else if (containerRequest.responseStatusCode != 204) {
1082 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1085 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1086 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1088 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1089 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1091 for (NSString *filePath in filenames) {
1093 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1096 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1097 precomposedStringWithCanonicalMapping];
1098 // Operation: Upload a local file
1099 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1100 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1101 if (operation.isCancelled) {
1105 NSError *error = nil;
1106 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1107 if (contentType == nil)
1108 contentType = @"application/octet-stream";
1110 NSLog(@"contentType detection error: %@", error);
1111 NSArray *hashes = nil;
1112 if (operation.isCancelled) {
1116 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1117 containerName:containerName
1118 objectName:objectName
1119 contentType:contentType
1125 sharingAccount:destinationNode.sharingAccount];
1126 if (!operation.isCancelled && objectRequest) {
1127 objectRequest.delegate = self;
1128 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1129 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1130 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1131 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1132 message:[messagePrefix stringByAppendingString:@" (0%)"]
1133 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1135 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1136 [NSDictionary dictionaryWithObjectsAndKeys:
1137 containerName, @"containerName",
1138 objectName, @"objectName",
1139 contentType, @"contentType",
1140 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1141 blockHash, @"blockHash",
1142 filePath, @"filePath",
1144 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1145 [NSNumber numberWithBool:YES], @"refresh",
1146 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1147 activity, @"activity",
1148 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1149 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1150 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1151 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1152 [NSNumber numberWithUnsignedInteger:10], @"retries",
1153 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1154 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1155 uploadNetworkQueue, @"networkQueue",
1156 @"upload", @"operationType",
1158 if (destinationNode.sharingAccount)
1159 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1160 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1164 [uploadQueue addOperation:operation];
1166 // Upload directory, confirm first
1167 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1168 [alert setMessageText:@"Upload directory"];
1169 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1170 [alert addButtonWithTitle:@"OK"];
1171 [alert addButtonWithTitle:@"Cancel"];
1172 NSInteger choice = [alert runModal];
1173 if (choice == NSAlertFirstButtonReturn) {
1174 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1175 precomposedStringWithCanonicalMapping];
1176 // Operation: Upload a local directory and its descendants
1177 // The resulting ASIPithosObjectRequests are chained through dependencies
1178 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1179 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1180 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) {
1206 ASIPithosObjectRequest *previousObjectRequest = nil;
1207 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1208 if (operation.isCancelled) {
1212 objectRequest.delegate = self;
1213 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1214 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1215 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1216 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1217 message:messagePrefix];
1218 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1219 [NSDictionary dictionaryWithObjectsAndKeys:
1220 [NSNumber numberWithBool:YES], @"refresh",
1221 activity, @"activity",
1222 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1223 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1224 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1225 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1226 [NSNumber numberWithUnsignedInteger:10], @"retries",
1227 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1228 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1229 uploadNetworkQueue, @"networkQueue",
1230 @"upload", @"operationType",
1232 if (previousObjectRequest)
1233 [objectRequest addDependency:previousObjectRequest];
1234 previousObjectRequest = objectRequest;
1235 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1237 if (!operation.isCancelled && objectRequests) {
1238 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1239 if (operation.isCancelled) {
1243 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1244 objectRequest.delegate = self;
1245 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1246 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1247 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1248 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1249 message:[messagePrefix stringByAppendingString:@" (0%)"]
1250 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1252 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1253 [NSDictionary dictionaryWithObjectsAndKeys:
1254 containerName, @"containerName",
1255 [objectNames objectAtIndex:i], @"objectName",
1256 [contentTypes objectAtIndex:i], @"contentType",
1257 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1258 blockHash, @"blockHash",
1259 [filePaths objectAtIndex:i], @"filePath",
1260 [hashesArrays objectAtIndex:i], @"hashes",
1261 [NSNumber numberWithBool:YES], @"refresh",
1262 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1263 activity, @"activity",
1264 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1265 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1266 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1267 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1268 [NSNumber numberWithUnsignedInteger:10], @"retries",
1269 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1270 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1271 uploadNetworkQueue, @"networkQueue",
1272 @"upload", @"operationType",
1274 if (destinationNode.sharingAccount)
1275 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1276 if (previousObjectRequest)
1277 [objectRequest addDependency:previousObjectRequest];
1278 previousObjectRequest = objectRequest;
1279 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1284 [uploadQueue addOperation:operation];
1292 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1293 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1294 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1296 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1297 NSString *objectNamePrefix;
1298 if ([destinationNode class] == [PithosSubdirNode class])
1299 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1301 objectNamePrefix = [NSString string];
1303 for (PithosNode *node in nodes) {
1304 if (([node class] == [PithosObjectNode class]) ||
1305 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1306 // Operation: Move an object or subdir/ node
1307 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1308 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1309 if (operation.isCancelled) {
1313 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1314 if ([node.pithosObject.name hasSuffix:@"/"])
1315 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1316 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1317 containerName:node.pithosContainer.name
1318 objectName:node.pithosObject.name
1319 destinationContainerName:containerName
1320 destinationObjectName:destinationObjectName
1322 if (!operation.isCancelled && objectRequest) {
1323 objectRequest.delegate = self;
1324 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1325 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1326 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1327 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1328 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1329 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1330 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1331 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1332 message:messagePrefix];
1333 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1334 [NSDictionary dictionaryWithObjectsAndKeys:
1335 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1336 activity, @"activity",
1337 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1338 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1339 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1340 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1341 [NSNumber numberWithUnsignedInteger:10], @"retries",
1342 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1343 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1344 moveNetworkQueue, @"networkQueue",
1345 @"move", @"operationType",
1347 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1351 [moveQueue addOperation:operation];
1352 } else if ([node class] == [PithosSubdirNode class]) {
1353 // Operation: Move a subdir node and its descendants
1354 // The resulting ASIPithosObjectRequests are chained through dependencies
1355 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1356 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1357 if (operation.isCancelled) {
1361 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1362 if (node.pithosObject.subdir)
1363 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1364 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1365 containerName:node.pithosContainer.name
1366 objectName:node.pithosObject.name
1367 destinationContainerName:containerName
1368 destinationObjectName:destinationObjectName
1370 if (!operation.isCancelled && objectRequests) {
1371 ASIPithosObjectRequest *previousObjectRequest = nil;
1372 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1373 if (operation.isCancelled) {
1377 objectRequest.delegate = self;
1378 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1379 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1380 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1381 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1382 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1383 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1384 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1385 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1386 message:messagePrefix];
1387 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1388 [NSDictionary dictionaryWithObjectsAndKeys:
1389 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1390 [NSNumber numberWithBool:YES], @"refresh",
1391 activity, @"activity",
1392 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1393 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1394 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1395 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1396 [NSNumber numberWithUnsignedInteger:10], @"retries",
1397 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1398 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1399 moveNetworkQueue, @"networkQueue",
1400 @"move", @"operationType",
1402 if (previousObjectRequest)
1403 [objectRequest addDependency:previousObjectRequest];
1404 previousObjectRequest = objectRequest;
1405 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1410 [moveQueue addOperation:operation];
1416 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1417 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1418 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1420 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1421 NSString *objectNamePrefix;
1422 if ([destinationNode class] == [PithosSubdirNode class])
1423 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1425 objectNamePrefix = [NSString string];
1427 for (PithosNode *node in nodes) {
1428 if (([node class] == [PithosObjectNode class]) ||
1429 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1430 // Operation: Copy an object or subdir/ node
1431 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1432 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1433 if (operation.isCancelled) {
1437 NSString *destinationObjectName;
1438 if (![destinationNode isEqualTo:node.parent]) {
1439 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1440 if ([node.pithosObject.name hasSuffix:@"/"])
1441 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1443 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1444 containerName:containerName
1445 objectName:node.pithosObject.name];
1447 if (operation.isCancelled) {
1451 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos
1452 containerName:node.pithosContainer.name
1453 objectName:node.pithosObject.name
1454 destinationContainerName:containerName
1455 destinationObjectName:destinationObjectName
1457 sharingAccount:node.sharingAccount];
1458 if (!operation.isCancelled && objectRequest) {
1459 objectRequest.delegate = self;
1460 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1461 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1462 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1463 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1464 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1465 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1466 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1467 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1468 message:messagePrefix];
1469 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1470 [NSDictionary dictionaryWithObjectsAndKeys:
1471 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1472 activity, @"activity",
1473 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1474 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1475 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1476 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1477 [NSNumber numberWithUnsignedInteger:10], @"retries",
1478 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1479 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1480 copyNetworkQueue, @"networkQueue",
1481 @"copy", @"operationType",
1483 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1487 [copyQueue addOperation:operation];
1488 } else if ([node class] == [PithosSubdirNode class]) {
1489 // Operation: Copy a subdir node and its descendants
1490 // The resulting ASIPithosObjectRequests are chained through dependencies
1491 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1492 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1493 if (operation.isCancelled) {
1497 NSString *destinationObjectName;
1498 if (![destinationNode isEqualTo:node.parent]) {
1499 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1500 if (node.pithosObject.subdir)
1501 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1503 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1504 containerName:containerName
1505 subdirName:node.pithosObject.name];
1507 if (operation.isCancelled) {
1511 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos
1512 containerName:node.pithosContainer.name
1513 objectName:node.pithosObject.name
1514 destinationContainerName:containerName
1515 destinationObjectName:destinationObjectName
1517 sharingAccount:node.sharingAccount];
1518 if (!operation.isCancelled && objectRequests) {
1519 ASIPithosObjectRequest *previousObjectRequest = nil;
1520 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1521 if (operation.isCancelled) {
1525 objectRequest.delegate = self;
1526 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1527 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1528 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1529 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1530 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1531 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1532 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1533 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1534 message:messagePrefix];
1535 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1536 [NSDictionary dictionaryWithObjectsAndKeys:
1537 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1538 activity, @"activity",
1539 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1540 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1541 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1542 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1543 [NSNumber numberWithUnsignedInteger:10], @"retries",
1544 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1545 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1546 copyNetworkQueue, @"networkQueue",
1547 @"copy", @"operationType",
1549 if (previousObjectRequest)
1550 [objectRequest addDependency:previousObjectRequest];
1551 previousObjectRequest = objectRequest;
1552 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1557 [copyQueue addOperation:operation];
1564 #pragma mark ASIHTTPRequestDelegate
1566 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1567 NSOperationQueue *callbackQueue;
1568 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1569 if ([operationType isEqualToString:@"move"])
1570 callbackQueue = moveCallbackQueue;
1571 else if ([operationType isEqualToString:@"copy"])
1572 callbackQueue = copyCallbackQueue;
1573 else if ([operationType isEqualToString:@"delete"])
1574 callbackQueue = deleteCallbackQueue;
1575 else if ([operationType isEqualToString:@"upload"])
1576 callbackQueue = uploadCallbackQueue;
1577 else if ([operationType isEqualToString:@"download"])
1578 callbackQueue = downloadCallbackQueue;
1580 dispatch_async(dispatch_get_main_queue(), ^{
1581 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1582 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1586 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1587 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1588 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1589 object:request] autorelease];
1590 operation.completionBlock = ^{
1591 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1592 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1593 dispatch_async(dispatch_get_main_queue(), ^{
1594 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1595 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1600 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1601 [callbackQueue addOperation:operation];
1604 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1605 if (request.isCancelled) {
1606 // Request has been cancelled
1607 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1608 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1609 withObject:request];
1611 NSOperationQueue *callbackQueue;
1612 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1613 if ([operationType isEqualToString:@"move"])
1614 callbackQueue = moveCallbackQueue;
1615 else if ([operationType isEqualToString:@"copy"])
1616 callbackQueue = copyCallbackQueue;
1617 else if ([operationType isEqualToString:@"delete"])
1618 callbackQueue = deleteCallbackQueue;
1619 else if ([operationType isEqualToString:@"upload"])
1620 callbackQueue = uploadCallbackQueue;
1621 else if ([operationType isEqualToString:@"download"])
1622 callbackQueue = downloadCallbackQueue;
1624 dispatch_async(dispatch_get_main_queue(), ^{
1625 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1626 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1630 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1631 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1632 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1633 object:request] autorelease];
1634 operation.completionBlock = ^{
1635 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1636 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1637 dispatch_async(dispatch_get_main_queue(), ^{
1638 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1639 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1644 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1645 [callbackQueue addOperation:operation];
1649 - (void)requestFailed:(ASIPithosRequest *)request {
1650 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1651 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1652 NSLog(@"Request failed: %@", request.url);
1653 if (operation.isCancelled) {
1657 if (request.isCancelled) {
1658 dispatch_async(dispatch_get_main_queue(), ^{
1659 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1660 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1665 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1667 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1668 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1669 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1670 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1672 dispatch_async(dispatch_get_main_queue(), ^{
1673 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1674 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1676 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1677 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1679 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1684 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1685 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1686 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1687 NSLog(@"Download finished: %@", objectRequest.url);
1688 if (operation.isCancelled) {
1689 [self requestFailed:objectRequest];
1690 } else if (objectRequest.responseStatusCode == 200) {
1691 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1692 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1693 NSUInteger totalBytes = activity.totalBytes;
1695 // XXX change contentLength to objectContentLength if it is fixed in the server
1696 if ([objectRequest contentLength] == 0) {
1697 // The check above was:
1698 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1699 // I checked for directory content types in order not to create a file in place of a directory,
1700 // but this callback method is not called in the case of a directory download.
1701 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1702 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1703 NSLog(@"Downloaded 0 bytes");
1704 NSFileManager *fileManager = [NSFileManager defaultManager];
1705 if (![fileManager fileExistsAtPath:filePath]) {
1706 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1707 dispatch_async(dispatch_get_main_queue(), ^{
1708 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1709 [alert setMessageText:@"Create File Error"];
1710 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1711 [alert addButtonWithTitle:@"OK"];
1718 NSUInteger currentBytes = [objectRequest objectContentLength];
1719 if (currentBytes == 0)
1720 currentBytes = totalBytes;
1721 dispatch_async(dispatch_get_main_queue(), ^{
1722 [activityFacility endActivity:activity
1723 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1724 totalBytes:totalBytes
1725 currentBytes:currentBytes];
1728 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1729 [self requestFailed:objectRequest];
1734 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1735 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1736 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1737 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1738 if (operation.isCancelled) {
1739 [self requestFailed:objectRequest];
1740 } else if (objectRequest.responseStatusCode == 201) {
1741 NSLog(@"Directory object created: %@", objectRequest.url);
1742 dispatch_async(dispatch_get_main_queue(), ^{
1743 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1744 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1745 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1746 [node forceRefresh];
1748 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1751 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1752 [self forceRefresh:self];
1753 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1754 [self refresh:self];
1757 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1758 [self requestFailed:objectRequest];
1763 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1764 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1765 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1766 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1767 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1768 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1769 NSUInteger totalBytes = activity.totalBytes;
1770 NSUInteger currentBytes = activity.currentBytes;
1771 if (operation.isCancelled) {
1772 [self requestFailed:objectRequest];
1773 } else if (objectRequest.responseStatusCode == 201) {
1774 NSLog(@"Object created: %@", objectRequest.url);
1775 dispatch_async(dispatch_get_main_queue(), ^{
1776 [activityFacility endActivity:activity
1777 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1778 totalBytes:totalBytes
1779 currentBytes:totalBytes];
1780 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1781 [node forceRefresh];
1783 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1786 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1787 [self forceRefresh:self];
1788 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1789 [self refresh:self];
1791 } else if (objectRequest.responseStatusCode == 409) {
1792 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1793 if (iteration == 0) {
1794 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1795 dispatch_async(dispatch_get_main_queue(), ^{
1796 [activityFacility endActivity:activity
1797 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1798 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1799 [alert setMessageText:@"Upload Timeout"];
1800 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1801 [objectRequest.userInfo objectForKey:@"objectName"]]];
1802 [alert addButtonWithTitle:@"OK"];
1808 NSLog(@"object is missing hashes: %@", objectRequest.url);
1809 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1810 withMissingHashes:[objectRequest hashes]];
1811 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1812 if (totalBytes >= [missingBlocks count]*blockSize)
1813 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1814 dispatch_async(dispatch_get_main_queue(), ^{
1815 [activityFacility updateActivity:activity
1816 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1817 totalBytes:totalBytes
1818 currentBytes:currentBytes];
1820 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1821 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1822 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1824 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1825 missingBlockIndex:missingBlockIndex
1826 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1827 newContainerRequest.delegate = self;
1828 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1829 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1830 newContainerRequest.userInfo = objectRequest.userInfo;
1831 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1832 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1833 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1834 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1835 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1836 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1837 [activityFacility updateActivity:activity
1838 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1839 totalBytes:activity.totalBytes
1840 currentBytes:(activity.currentBytes + size)];
1842 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1844 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1845 [self requestFailed:objectRequest];
1850 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1851 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1852 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1853 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1854 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1855 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1856 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1857 if (operation.isCancelled) {
1858 [self requestFailed:containerRequest];
1859 } else if (containerRequest.responseStatusCode == 202) {
1860 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1861 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1862 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1863 if (missingBlockIndex == NSNotFound) {
1864 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1865 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1866 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1867 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1868 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1870 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1871 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1874 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1875 newObjectRequest.delegate = self;
1876 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1877 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1878 newObjectRequest.userInfo = containerRequest.userInfo;
1879 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1880 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1881 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1882 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1883 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1885 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1886 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1887 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1888 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1889 missingBlockIndex:missingBlockIndex
1890 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1891 newContainerRequest.delegate = self;
1892 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1893 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1894 newContainerRequest.userInfo = containerRequest.userInfo;
1895 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1896 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1897 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1898 [activityFacility updateActivity:activity
1899 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1900 totalBytes:activity.totalBytes
1901 currentBytes:(activity.currentBytes + size)];
1903 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1906 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1907 [self requestFailed:containerRequest];
1912 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1913 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1914 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1915 NSLog(@"Move object finished: %@", objectRequest.url);
1916 if (operation.isCancelled) {
1917 [self requestFailed:objectRequest];
1918 } else if (objectRequest.responseStatusCode == 201) {
1919 dispatch_async(dispatch_get_main_queue(), ^{
1920 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1921 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1922 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1923 [node forceRefresh];
1925 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1928 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1929 [self forceRefresh:self];
1930 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1931 [self refresh:self];
1934 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1935 [self requestFailed:objectRequest];
1940 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1941 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1942 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1943 NSLog(@"Copy object finished: %@", objectRequest.url);
1944 if (operation.isCancelled) {
1945 [self requestFailed:objectRequest];
1946 } else if (objectRequest.responseStatusCode == 201) {
1947 dispatch_async(dispatch_get_main_queue(), ^{
1948 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1949 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1950 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1951 [node forceRefresh];
1953 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1956 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1957 [self forceRefresh:self];
1958 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1959 [self refresh:self];
1962 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1963 [self requestFailed:objectRequest];
1968 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1969 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1970 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1971 NSLog(@"Delete object finished: %@", objectRequest.url);
1972 if (operation.isCancelled) {
1973 [self requestFailed:objectRequest];
1974 } else if (objectRequest.responseStatusCode == 204) {
1975 dispatch_async(dispatch_get_main_queue(), ^{
1976 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1977 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1978 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1979 [node forceRefresh];
1981 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1984 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1985 [self forceRefresh:self];
1986 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1987 [self refresh:self];
1990 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1991 [self requestFailed:objectRequest];
1997 #pragma mark NSSplitViewDelegate
1999 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
2000 if (splitView == verticalSplitView)
2003 return ([horizontalSplitView bounds].size.height - 108);
2006 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
2007 if (splitView == verticalSplitView)
2010 return ([horizontalSplitView bounds].size.height - 108);
2013 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2014 if (splitView == verticalSplitView) {
2015 if (proposedPosition < 120)
2017 else if (proposedPosition > 220)
2020 return proposedPosition;
2022 return ([horizontalSplitView bounds].size.height - 108);
2027 #pragma mark NSOutlineViewDataSource
2029 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2030 if (!browserInitialized)
2034 if (item == containersNode)
2035 return containersNodeChildren.count;
2036 if (item == sharedNode)
2041 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2042 if (!browserInitialized)
2045 return (!index ? containersNode : sharedNode);
2046 if (item == sharedNode)
2047 return (!index ? mySharedNode : othersSharedNode);
2048 return [containersNodeChildren objectAtIndex:index];
2051 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2052 if ((item == containersNode) || (item == sharedNode))
2057 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2058 PithosNode *node = (PithosNode *)item;
2062 #pragma mark Drag and Drop destination
2064 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2065 validateDrop:(id<NSDraggingInfo>)info
2066 proposedItem:(id)item
2067 proposedChildIndex:(NSInteger)index {
2068 NSDragOperation result = NSDragOperationNone;
2069 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2071 PithosNode *dropNode = (PithosNode *)item;
2072 if ([dropNode class] != [PithosContainerNode class])
2074 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2075 result = NSDragOperationCopy;
2076 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2077 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2078 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2079 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2080 if (![dropNode isEqualTo:draggedParentNode])
2081 result = NSDragOperationMove;
2082 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2083 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2084 result = NSDragOperationCopy;
2090 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2091 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2092 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2093 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2094 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2095 PithosNode *node = (PithosNode *)item;
2096 NSLog(@"drag in node: %@", node.url);
2097 return [self uploadFiles:filenames toNode:node];
2099 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2100 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2101 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2102 PithosNode *node = (PithosNode *)item;
2103 NSLog(@"drag local node: %@", node.url);
2104 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2105 ([info draggingSourceOperationMask] & NSDragOperationMove))
2106 return [self moveNodes:draggedNodes toNode:node];
2107 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2108 return [self copyNodes:draggedNodes toNode:node];
2115 #pragma mark NSOutlineViewDelegate
2117 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2118 if ((item == containersNode) || (item == sharedNode))
2123 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2124 if ((item == containersNode) || (item == sharedNode))
2129 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2130 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2133 [browser loadColumnZero];
2139 #pragma mark NSMenuDelegate
2141 - (void)menuNeedsUpdate:(NSMenu *)menu {
2142 [menu removeAllItems];
2143 NSMenuItem *menuItem;
2144 NSString *menuItemTitle;
2145 BOOL nodeContextMenu = NO;
2146 PithosNode *menuNode = nil;
2147 NSMutableArray *menuNodes;
2148 if (menu == browserMenu) {
2149 NSInteger column = [browser clickedColumn];
2150 NSInteger row = [browser clickedRow];
2151 if ((column == -1) || (row == -1)) {
2152 // General context menu
2153 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2154 if ([menuNodesIndexPaths count] == 0) {
2155 menuNode = [browser parentForItemsInColumn:0];
2156 } else if (([menuNodesIndexPaths count] != 1) ||
2157 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2158 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2160 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2163 // Node context menu
2164 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2165 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2166 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2167 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2168 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2169 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2172 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2174 nodeContextMenu = YES;
2176 } else if (menu == outlineViewMenu) {
2177 NSInteger row = [outlineView clickedRow];
2179 row = [outlineView selectedRow];
2182 menuNode = [outlineView itemAtRow:row];
2185 if (!nodeContextMenu) {
2186 // General context menu
2187 if (([menuNode class] == [PithosAccountNode class]) ||
2188 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2189 ([menuNode class] == [PithosEmptyNode class]))
2192 if (!menuNode.shared && !menuNode.sharingAccount) {
2193 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2194 [menuItem setRepresentedObject:menuNode];
2195 [menu addItem:menuItem];
2196 [menu addItem:[NSMenuItem separatorItem]];
2199 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2200 [menu addItem:menuItem];
2201 [menu addItem:[NSMenuItem separatorItem]];
2203 menuItem = [[[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2204 action:@selector(menuGetInfo:)
2205 keyEquivalent:@""] autorelease];
2206 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2207 [menu addItem:menuItem];
2209 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2210 (([menuNode class] == [PithosContainerNode class]) ||
2211 (([menuNode class] == [PithosSubdirNode class]) &&
2212 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2213 NSUInteger clipboardNodesCount = [clipboardNodes count];
2214 if (clipboardNodesCount == 0) {
2215 self.clipboardNodes = nil;
2216 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2217 if (clipboardNodesCount == 1)
2218 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2220 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2221 [menu addItem:[NSMenuItem separatorItem]];
2222 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2223 [menuItem setRepresentedObject:menuNode];
2224 [menu addItem:menuItem];
2228 // Node context menu
2229 NSUInteger menuNodesCount = [menuNodes count];
2230 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2232 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2233 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""] autorelease];
2234 [menuItem setRepresentedObject:menuNodes];
2235 [menu addItem:menuItem];
2236 [menu addItem:[NSMenuItem separatorItem]];
2238 // Move to Trash (pithos container only)
2240 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2241 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2242 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2243 action:@selector(menuMoveToTrash:)
2244 keyEquivalent:@""] autorelease];
2245 [menuItem setRepresentedObject:menuNodes];
2246 [menu addItem:menuItem];
2248 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2249 [menuItem setRepresentedObject:menuNodes];
2250 [menu addItem:menuItem];
2251 [menu addItem:[NSMenuItem separatorItem]];
2254 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2255 [menu addItem:menuItem];
2257 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2258 [menu addItem:[NSMenuItem separatorItem]];
2259 menuItem = [[[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2260 action:@selector(menuGetInfo:)
2261 keyEquivalent:@""] autorelease];
2262 [menuItem setRepresentedObject:menuNodes];
2263 [menu addItem:menuItem];
2265 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2266 [menu addItem:[NSMenuItem separatorItem]];
2269 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2270 if (menuNodesCount == 1)
2271 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2273 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2274 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2275 [menuItem setRepresentedObject:menuNodes];
2276 [menu addItem:menuItem];
2279 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2280 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2281 if (menuNodesCount == 1)
2282 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2284 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2285 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2286 [menuItem setRepresentedObject:menuNodes];
2287 [menu addItem:menuItem];
2290 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2291 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2292 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2293 NSUInteger clipboardNodesCount = [clipboardNodes count];
2294 if (clipboardNodesCount == 0) {
2295 self.clipboardNodes = nil;
2296 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2297 if (clipboardNodesCount == 1)
2298 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2300 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2301 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2302 [menuItem setRepresentedObject:firstMenuNode];
2303 [menu addItem:menuItem];
2310 #pragma mark NSMenuValidation
2312 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2313 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2314 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2315 if ([menuNodesIndexPaths count] == 0)
2318 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2319 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2320 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2321 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2322 ((menuItem.action == @selector(delete:)) &&
2323 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2324 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2327 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2328 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2329 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2331 menuItem.representedObject = menuNodes;
2332 } else if (menuItem.action == @selector(paste:)) {
2333 if (!clipboardNodes || ![clipboardNodes count])
2336 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2337 PithosNode *menuNode;
2338 if ([menuNodesIndexPaths count] == 0)
2339 menuNode = [browser parentForItemsInColumn:0];
2340 else if (([menuNodesIndexPaths count] != 1) ||
2341 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2342 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2344 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2346 if (menuNode.shared || menuNode.sharingAccount ||
2347 (([menuNode class] != [PithosContainerNode class]) &&
2348 (([menuNode class] != [PithosSubdirNode class]) ||
2349 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2350 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2353 menuItem.representedObject = menuNode;
2358 - (void)cut:(NSMenuItem *)sender {
2359 [self menuCut:sender];
2362 - (void)copy:(NSMenuItem *)sender {
2363 [self menuCopy:sender];
2366 - (void)paste:(NSMenuItem *)sender {
2367 [self menuPaste:sender];
2370 - (void)delete:(NSMenuItem *)sender {
2371 if (sender.tag == 0)
2372 [self menuMoveToTrash:sender];
2374 [self menuDelete:sender];
2378 #pragma mark Menu Actions
2380 - (void)menuNewFolder:(NSMenuItem *)sender {
2381 PithosNode *node = (PithosNode *)[sender representedObject];
2382 if ([node class] == [PithosContainerNode class]) {
2383 // Operation: Create (upload) a new root application/directory object
2384 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2385 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2386 if (operation.isCancelled) {
2390 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2391 containerName:node.pithosContainer.name
2392 subdirName:@"untitled folder"];
2393 NSString *fileName = [safeObjectName lastPathComponent];
2394 if (operation.isCancelled) {
2398 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2399 containerName:node.pithosContainer.name
2400 objectName:safeObjectName
2402 contentType:@"application/directory"
2404 contentDisposition:nil
2407 isPublic:ASIPithosObjectRequestPublicIgnore
2409 data:[NSData data]];
2410 objectRequest.delegate = self;
2411 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2412 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2413 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2414 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2415 message:messagePrefix];
2416 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2417 fileName, @"fileName",
2418 [NSArray arrayWithObject:node], @"refreshNodes",
2419 [NSNumber numberWithBool:YES], @"refresh",
2420 activity, @"activity",
2421 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2422 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2423 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2424 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2425 [NSNumber numberWithUnsignedInteger:10], @"retries",
2426 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2427 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2428 uploadNetworkQueue, @"networkQueue",
2429 @"upload", @"operationType",
2431 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2434 [uploadQueue addOperation:operation];
2435 } else if (([node class] == [PithosSubdirNode class]) &&
2436 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2437 // Operation: Create (upload) a new aplication/directory object
2438 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2439 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2440 if (operation.isCancelled) {
2444 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2445 containerName:node.pithosContainer.name
2446 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2447 NSString *fileName = [safeObjectName lastPathComponent];
2448 if (operation.isCancelled) {
2452 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2453 containerName:node.pithosContainer.name
2454 objectName:safeObjectName
2456 contentType:@"application/directory"
2458 contentDisposition:nil
2461 isPublic:ASIPithosObjectRequestPublicIgnore
2463 data:[NSData data]];
2464 objectRequest.delegate = self;
2465 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2466 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2467 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2468 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2469 message:messagePrefix];
2470 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2471 fileName, @"fileName",
2472 [NSArray arrayWithObject:node], @"refreshNodes",
2473 [NSNumber numberWithBool:YES], @"refresh",
2474 activity, @"activity",
2475 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2476 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2477 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2478 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2479 [NSNumber numberWithUnsignedInteger:10], @"retries",
2480 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2481 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2482 uploadNetworkQueue, @"networkQueue",
2483 @"upload", @"operationType",
2485 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2488 [uploadQueue addOperation:operation];
2492 - (void)menuGetInfo:(NSMenuItem *)sender {
2493 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2494 [node showPithosNodeInfo:sender];
2498 - (void)menuDownload:(NSMenuItem *)sender {
2499 NSArray *nodes = (NSArray *)[sender representedObject];
2500 PithosNode *firstNode = [nodes objectAtIndex:0];
2501 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2502 NSSavePanel *save = [NSSavePanel savePanel];
2503 save.nameFieldStringValue = firstNode.displayName;
2504 int result = [save runModal];
2505 if (result == NSOKButton) {
2506 NSString *destinationPath = save.URL.path;
2507 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2508 NSString *newFileName = [destinationPath lastPathComponent];
2509 if ([destinationPath hasSuffix:@"/"])
2510 newFileName = [newFileName stringByAppendingString:@"/"];
2511 if ([firstNode.displayName isEqualToString:newFileName])
2513 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2516 NSOpenPanel *open = [NSOpenPanel openPanel];
2517 open.canChooseFiles = NO;
2518 open.canChooseDirectories = YES;
2519 open.canCreateDirectories = YES;
2520 int result = [open runModal];
2521 if (result == NSOKButton) {
2522 NSString *directoryPath = open.URL.path;
2523 for (PithosNode *node in nodes) {
2524 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2530 - (void)menuDelete:(NSMenuItem *)sender {
2531 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2532 if (([node class] == [PithosObjectNode class]) ||
2533 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2534 // Operation: Delete an object or subdir/ node
2535 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2536 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2537 if (operation.isCancelled) {
2541 NSString *fileName = [node.pithosObject.name lastPathComponent];
2542 if ([node.pithosObject.name hasSuffix:@"/"])
2543 fileName = [fileName stringByAppendingString:@"/"];
2544 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2545 containerName:node.pithosContainer.name
2546 objectName:node.pithosObject.name];
2547 if (operation.isCancelled) {
2551 objectRequest.delegate = self;
2552 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2553 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2554 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2555 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2556 message:messagePrefix];
2557 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2558 fileName, @"fileName",
2559 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2560 activity, @"activity",
2561 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2562 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2563 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2564 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2565 [NSNumber numberWithUnsignedInteger:10], @"retries",
2566 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2567 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2568 deleteNetworkQueue, @"networkQueue",
2569 @"delete", @"operationType",
2571 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2574 [deleteQueue addOperation:operation];
2575 } else if ([node class] == [PithosSubdirNode class]) {
2576 // Operation: Delete a subdir node and its descendants
2577 // The resulting ASIPithosObjectRequests are chained through dependencies
2578 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2579 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2580 if (operation.isCancelled) {
2584 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2585 containerName:node.pithosContainer.name
2586 objectName:node.pithosObject.name];
2587 if (!operation.isCancelled && objectRequests) {
2588 ASIPithosObjectRequest *previousObjectRequest = nil;
2589 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2590 if (operation.isCancelled) {
2594 objectRequest.delegate = self;
2595 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2596 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2597 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2598 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2599 message:messagePrefix];
2600 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2601 [NSDictionary dictionaryWithObjectsAndKeys:
2602 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2603 activity, @"activity",
2604 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2605 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2606 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2607 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2608 [NSNumber numberWithUnsignedInteger:10], @"retries",
2609 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2610 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2611 deleteNetworkQueue, @"networkQueue",
2612 @"delete", @"operationType",
2614 if (previousObjectRequest)
2615 [objectRequest addDependency:previousObjectRequest];
2616 previousObjectRequest = objectRequest;
2617 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2622 [deleteQueue addOperation:operation];
2627 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2628 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2629 if (([node class] == [PithosObjectNode class]) ||
2630 (([node class] == [PithosSubdirNode class]) &&
2631 !node.pithosObject.subdir &&
2632 [node.pithosObject.name hasSuffix:@"/"])) {
2633 // Operation: Move to trash an object or subdir/ node
2634 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2635 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2636 if (operation.isCancelled) {
2640 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2641 containerName:@"trash"
2642 objectName:node.pithosObject.name];
2643 if (!operation.isCancelled && safeObjectName) {
2644 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2645 containerName:node.pithosContainer.name
2646 objectName:node.pithosObject.name
2647 destinationContainerName:@"trash"
2648 destinationObjectName:safeObjectName
2650 if (!operation.isCancelled && objectRequest) {
2651 objectRequest.delegate = self;
2652 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2653 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2654 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2655 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2656 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2657 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2658 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2659 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2660 message:messagePrefix];
2661 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2662 [NSDictionary dictionaryWithObjectsAndKeys:
2663 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2664 activity, @"activity",
2665 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2666 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2667 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2668 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2669 [NSNumber numberWithUnsignedInteger:10], @"retries",
2670 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2671 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2672 moveNetworkQueue, @"networkQueue",
2673 @"move", @"operationType",
2675 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2680 [moveQueue addOperation:operation];
2681 } else if ([node class] == [PithosSubdirNode class]) {
2682 // Operation: Move to trash a subdir node and its descendants
2683 // The resulting ASIPithosObjectRequests are chained through dependencies
2684 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2685 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2686 if (operation.isCancelled) {
2690 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2691 containerName:@"trash"
2692 subdirName:node.pithosObject.name];
2693 if (!operation.isCancelled && safeObjectName) {
2694 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2695 containerName:node.pithosContainer.name
2696 objectName:node.pithosObject.name
2697 destinationContainerName:@"trash"
2698 destinationObjectName:safeObjectName
2700 if (!operation.isCancelled && objectRequests) {
2701 ASIPithosObjectRequest *previousObjectRequest = nil;
2702 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2703 if (operation.isCancelled) {
2707 objectRequest.delegate = self;
2708 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2709 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2710 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2711 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2712 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2713 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2714 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2715 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2716 message:messagePrefix];
2717 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2718 [NSDictionary dictionaryWithObjectsAndKeys:
2719 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2720 activity, @"activity",
2721 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2722 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2723 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2724 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2725 [NSNumber numberWithUnsignedInteger:10], @"retries",
2726 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2727 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2728 moveNetworkQueue, @"networkQueue",
2729 @"move", @"operationType",
2731 if (previousObjectRequest)
2732 [objectRequest addDependency:previousObjectRequest];
2733 previousObjectRequest = objectRequest;
2734 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2740 [moveQueue addOperation:operation];
2745 - (void)menuCut:(NSMenuItem *)sender {
2746 self.clipboardNodes = (NSArray *)[sender representedObject];
2747 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2748 self.clipboardCopy = NO;
2751 - (void)menuCopy:(NSMenuItem *)sender {
2752 self.clipboardNodes = (NSArray *)[sender representedObject];
2753 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2754 self.clipboardCopy = YES;
2757 - (void)menuPaste:(NSMenuItem *)sender {
2758 if (!clipboardNodes || ![clipboardNodes count])
2760 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2761 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2762 if (clipboardCopy) {
2763 [self copyNodes:localClipboardNodes toNode:dropNode];
2764 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2765 self.clipboardNodes = nil;
2766 self.clipboardParentNode = nil;
2767 [self moveNodes:localClipboardNodes toNode:dropNode];
2772 #pragma mark PithosActivityFacilityDelegate
2774 - (void)activityUpdate:(NSDictionary *)info {
2775 NSString *message = [info objectForKey:@"message"];
2776 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2777 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2778 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2779 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2780 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2781 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2782 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2783 if (runningActivitiesCount && totalBytes) {
2784 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2785 [activityProgressIndicator startAnimation:self];
2787 [activityProgressIndicator setDoubleValue:1.0];
2788 [activityProgressIndicator stopAnimation:self];
2792 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2793 [activityTextField setStringValue:message];