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 dispatch_async(dispatch_get_main_queue(), ^{
474 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
477 refreshAccountNode = YES;
480 if (!containerTrashFound) {
482 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
483 containerName:@"trash"];
484 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
486 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
487 if ([containerRequest error]) {
488 dispatch_async(dispatch_get_main_queue(), ^{
489 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
492 refreshAccountNode = YES;
496 if (refreshAccountNode)
497 [accountNode refresh];
499 [outlineView reloadData];
501 // Expand the folder outline view
502 [outlineView expandItem:nil expandChildren:YES];
504 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
505 rootNode = [containersNodeChildren objectAtIndex:0];
506 [browser loadColumnZero];
513 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
520 - (IBAction)forceRefresh:(id)sender {
524 [accountNode forceRefresh];
525 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
526 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
527 node.forcedRefresh = YES;
528 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
530 [browser validateVisibleColumns];
533 - (IBAction)refresh:(id)sender {
536 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
537 [self forceRefresh:sender];
540 [accountNode refresh];
541 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
542 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
544 [browser validateVisibleColumns];
549 #pragma mark NSBrowserDelegate
551 - (id)rootItemForBrowser:(NSBrowser *)browser {
555 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
556 PithosNode *node = (PithosNode *)item;
557 return node.children.count;
560 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
561 PithosNode *node = (PithosNode *)item;
562 return [node.children objectAtIndex:index];
565 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
566 PithosNode *node = (PithosNode *)item;
567 return node.isLeafItem;
570 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
571 PithosNode *node = (PithosNode *)item;
575 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
576 if (sharedPreviewController == nil)
577 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
578 return sharedPreviewController;
581 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
582 // if (!forUserResize) {
583 // id item = [browser parentForItemsInColumn:columnIndex];
584 // if ([self browser:browser isLeafItem:item]) {
585 // suggestedWidth = 200;
588 // return suggestedWidth;
591 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
597 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
598 PithosNode *node = (PithosNode *)item;
599 if (node.shared || node.sharingAccount ||
600 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
606 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
608 PithosNode *node = (PithosNode *)item;
609 NSString *newName = (NSString *)object;
610 NSUInteger newNameLength = [newName length];
611 NSRange firstSlashRange = [newName rangeOfString:@"/"];
612 if ((newNameLength == 0) ||
613 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
614 ([newName isEqualToString:node.displayName])) {
617 if (([node class] == [PithosObjectNode class]) ||
618 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
619 // Operation: Rename (move) an object or subdir/ node
620 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
621 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
622 if (operation.isCancelled) {
626 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
627 if ([newName hasSuffix:@"/"])
628 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
629 NSError *error = nil;
631 if ([PithosUtilities objectExistsAtPithos:pithos
632 containerName:node.pithosContainer.name
633 objectName:destinationObjectName
635 isDirectory:&isDirectory
636 sharingAccount:nil]) {
637 dispatch_async(dispatch_get_main_queue(), ^{
638 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
639 [alert setMessageText:@"Name Taken"];
640 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
641 [alert addButtonWithTitle:@"OK"];
650 if (operation.isCancelled) {
654 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
655 containerName:node.pithosContainer.name
656 objectName:node.pithosObject.name
657 destinationContainerName:node.pithosContainer.name
658 destinationObjectName:destinationObjectName
660 if (!operation.isCancelled && objectRequest) {
661 objectRequest.delegate = self;
662 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
663 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
664 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
665 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
666 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
667 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
668 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
669 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
670 message:messagePrefix];
671 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
672 [NSDictionary dictionaryWithObjectsAndKeys:
673 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
674 [NSNumber numberWithBool:YES], @"refresh",
675 activity, @"activity",
676 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
677 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
678 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
679 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
680 [NSNumber numberWithUnsignedInteger:10], @"retries",
681 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
682 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
683 moveNetworkQueue, @"networkQueue",
684 @"move", @"operationType",
686 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
690 [moveQueue addOperation:operation];
691 } else if ([node class] == [PithosSubdirNode class]) {
692 if (firstSlashRange.length == 1)
694 // Operation: Rename (move) a subdir node and its descendants
695 // The resulting ASIPithosObjectRequests are chained through dependencies
696 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
697 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
698 if (operation.isCancelled) {
702 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
703 NSError *error = nil;
705 if ([PithosUtilities objectExistsAtPithos:pithos
706 containerName:node.pithosContainer.name
707 objectName:destinationObjectName
709 isDirectory:&isDirectory
710 sharingAccount:nil]) {
711 dispatch_async(dispatch_get_main_queue(), ^{
712 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
713 [alert setMessageText:@"Name Taken"];
714 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
715 [alert addButtonWithTitle:@"OK"];
724 if (operation.isCancelled) {
728 if (node.pithosObject.subdir)
729 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
730 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
731 containerName:node.pithosContainer.name
732 objectName:node.pithosObject.name
733 destinationContainerName:node.pithosContainer.name
734 destinationObjectName:destinationObjectName
736 if (!operation.isCancelled && objectRequests) {
737 ASIPithosObjectRequest *previousObjectRequest = nil;
738 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
739 if (operation.isCancelled) {
743 objectRequest.delegate = self;
744 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
745 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
746 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
747 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
748 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
749 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
750 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
751 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
752 message:messagePrefix];
753 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
754 [NSDictionary dictionaryWithObjectsAndKeys:
755 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
756 [NSNumber numberWithBool:YES], @"refresh",
757 activity, @"activity",
758 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
759 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
760 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
761 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
762 [NSNumber numberWithUnsignedInteger:10], @"retries",
763 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
764 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
765 moveNetworkQueue, @"networkQueue",
766 @"move", @"operationType",
768 if (previousObjectRequest)
769 [objectRequest addDependency:previousObjectRequest];
770 previousObjectRequest = objectRequest;
771 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
776 [moveQueue addOperation:operation];
780 #pragma mark Drag and Drop source
782 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
783 withEvent:(NSEvent *)event {
784 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
785 __block BOOL result = YES;
786 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
787 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
788 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
796 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
797 toPasteboard:(NSPasteboard *)pasteboard {
798 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
799 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
800 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
801 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
802 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
803 [propertyList addObject:[node.pithosObject.name pathExtension]];
804 [nodes addObject:node];
807 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
808 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
809 self.draggedNodes = nodes;
810 self.draggedParentNode = [browser parentForItemsInColumn:column];
814 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
815 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
816 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
817 for (PithosNode *node in draggedNodes) {
818 [names addObject:node.displayName];
819 // If the node is a subdir ask if the whole tree should be downloaded
820 if ([node class] == [PithosSubdirNode class]) {
821 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
822 [alert setMessageText:@"Download directory"];
823 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
824 [alert addButtonWithTitle:@"OK"];
825 [alert addButtonWithTitle:@"Cancel"];
826 NSInteger choice = [alert runModal];
827 if (choice == NSAlertFirstButtonReturn)
828 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
830 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
836 #pragma mark Drag and Drop destination
838 - (NSDragOperation)browser:aBrowser
839 validateDrop:(id<NSDraggingInfo>)info
840 proposedRow:(NSInteger *)row
841 column:(NSInteger *)column
842 dropOperation:(NSBrowserDropOperation *)dropOperation {
843 NSDragOperation result = NSDragOperationNone;
844 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
845 // For a drop above, the drop is redirected to the parent item
846 if (*dropOperation == NSBrowserDropAbove)
848 // Only allow dropping in folders
850 PithosNode *dropNode;
852 // Check if the node is not a folder and if so redirect to the parent item
853 dropNode = [browser itemAtRow:*row inColumn:*column];
854 if ([dropNode class] == [PithosObjectNode class])
858 dropNode = [browser parentForItemsInColumn:*column];
860 if (!dropNode.shared &&
861 (!dropNode.sharingAccount ||
862 ([dropNode class] == [PithosSubdirNode class]) ||
863 ([dropNode class] == [PithosContainerNode class]))) {
864 *dropOperation = NSBrowserDropOn;
865 result = NSDragOperationCopy;
868 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
869 // For a drop above, the drop is redirected to the parent item
870 if (*dropOperation == NSBrowserDropAbove)
872 // Only allow dropping in folders
874 PithosNode *dropNode;
876 // Check if the node is not a folder and if so redirect to the parent item
877 dropNode = [browser itemAtRow:*row inColumn:*column];
878 if ([dropNode class] == [PithosObjectNode class])
882 dropNode = [browser parentForItemsInColumn:*column];
884 if (!dropNode.shared && !dropNode.sharingAccount) {
885 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
886 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
887 if ((([dropNode class] == [PithosContainerNode class]) ||
888 dropNode.pithosObject.subdir ||
889 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
890 ![dropNode isEqualTo:draggedParentNode]) {
891 // ![dropNode isEqualTo:draggedParentNode] &&
892 // ![draggedNodes containsObject:dropNode]) {
893 result = NSDragOperationMove;
895 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
896 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
897 if (([dropNode class] == [PithosContainerNode class]) ||
898 dropNode.pithosObject.subdir ||
899 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
900 result = NSDragOperationCopy;
909 - (BOOL)browser:(NSBrowser *)aBrowser
910 acceptDrop:(id<NSDraggingInfo>)info
912 column:(NSInteger)column
913 dropOperation:(NSBrowserDropOperation)dropOperation {
914 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
915 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
916 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
917 if ((column != -1) && (filenames != nil)) {
920 node = [browser itemAtRow:row inColumn:column];
922 node = [browser parentForItemsInColumn:column];
923 NSLog(@"drag in node: %@", node.url);
924 return [self uploadFiles:filenames toNode:node];
926 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
927 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
928 if ((column != -1) && (draggedNodes != nil)) {
931 node = [browser itemAtRow:row inColumn:column];
933 node = [browser parentForItemsInColumn:column];
934 NSLog(@"drag local node: %@", node.url);
935 if ([info draggingSourceOperationMask] & NSDragOperationMove)
936 return [self moveNodes:draggedNodes toNode:node];
937 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
938 return [self copyNodes:draggedNodes toNode:node];
945 #pragma mark Drag and Drop methods
947 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
948 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
949 if ([node class] == [PithosSubdirNode class]) {
950 // XXX newFilename and version are ignored in the case of a subdir node for now
951 // Operation: Download a subdir node and its descendants
952 // The resulting ASIPithosObjectRequests are chained through dependencies
953 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
954 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
955 if (operation.isCancelled) {
959 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
960 containerName:node.pithosContainer.name
961 objectName:node.pithosObject.name
963 checkIfExists:checkIfExists
964 sharingAccount:node.sharingAccount];
965 if (!operation.isCancelled && objectRequests) {
966 ASIPithosObjectRequest *previousObjectRequest = nil;
967 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
968 if (operation.isCancelled) {
972 objectRequest.delegate = self;
973 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
974 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
975 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
976 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
977 message:[messagePrefix stringByAppendingString:@" (0%)"]
978 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
980 dispatch_async(dispatch_get_main_queue(), ^{
981 [activityFacility updateActivity:activity withMessage:activity.message];
983 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
984 [NSDictionary dictionaryWithObjectsAndKeys:
985 activity, @"activity",
986 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
987 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
988 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
989 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
990 [NSNumber numberWithUnsignedInteger:10], @"retries",
991 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
992 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
993 downloadNetworkQueue, @"networkQueue",
994 @"download", @"operationType",
996 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
997 [activityFacility updateActivity:activity
998 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
999 totalBytes:activity.totalBytes
1000 currentBytes:(activity.currentBytes + size)];
1002 if (previousObjectRequest)
1003 [objectRequest addDependency:previousObjectRequest];
1004 previousObjectRequest = objectRequest;
1005 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1010 [downloadQueue addOperation:operation];
1011 } else if ([node class] == [PithosObjectNode class]) {
1012 // Operation: Download an object node
1013 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1014 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1015 if (operation.isCancelled) {
1019 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
1020 containerName:node.pithosContainer.name
1021 objectName:node.pithosObject.name
1024 withNewFileName:newFileName
1025 checkIfExists:checkIfExists
1026 sharingAccount:node.sharingAccount];
1027 if (!operation.isCancelled && objectRequest) {
1028 objectRequest.delegate = self;
1029 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1030 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1031 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1032 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1033 message:[messagePrefix stringByAppendingString:@" (0%)"]
1034 totalBytes:node.pithosObject.bytes
1036 dispatch_async(dispatch_get_main_queue(), ^{
1037 [activityFacility updateActivity:activity withMessage:activity.message];
1039 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1040 [NSDictionary dictionaryWithObjectsAndKeys:
1041 activity, @"activity",
1042 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1043 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1044 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1045 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1046 [NSNumber numberWithUnsignedInteger:10], @"retries",
1047 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1048 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1049 downloadNetworkQueue, @"networkQueue",
1050 @"download", @"operationType",
1052 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1053 [activityFacility updateActivity:activity
1054 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1055 totalBytes:activity.totalBytes
1056 currentBytes:(activity.currentBytes + size)];
1058 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1062 [downloadQueue addOperation:operation];
1066 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1067 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1069 NSFileManager *fileManager = [NSFileManager defaultManager];
1070 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1071 NSString *objectNamePrefix;
1072 if ([destinationNode class] == [PithosSubdirNode class])
1073 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1075 objectNamePrefix = [NSString string];
1076 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1077 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1078 containerName:containerName];
1079 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1081 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1082 if ([containerRequest error]) {
1083 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1085 } else if (containerRequest.responseStatusCode != 204) {
1086 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1089 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1090 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1092 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1093 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1095 for (NSString *filePath in filenames) {
1097 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1100 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1101 precomposedStringWithCanonicalMapping];
1102 // Operation: Upload a local file
1103 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1104 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1105 if (operation.isCancelled) {
1109 NSError *error = nil;
1110 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1111 if (contentType == nil)
1112 contentType = @"application/octet-stream";
1114 NSLog(@"contentType detection error: %@", error);
1115 NSArray *hashes = nil;
1116 if (operation.isCancelled) {
1120 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1121 containerName:containerName
1122 objectName:objectName
1123 contentType:contentType
1129 sharingAccount:destinationNode.sharingAccount];
1130 if (!operation.isCancelled && objectRequest) {
1131 objectRequest.delegate = self;
1132 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1133 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1134 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1135 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1136 message:[messagePrefix stringByAppendingString:@" (0%)"]
1137 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1139 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1140 [NSDictionary dictionaryWithObjectsAndKeys:
1141 containerName, @"containerName",
1142 objectName, @"objectName",
1143 contentType, @"contentType",
1144 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1145 blockHash, @"blockHash",
1146 filePath, @"filePath",
1148 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1149 [NSNumber numberWithBool:YES], @"refresh",
1150 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1151 activity, @"activity",
1152 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1153 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1154 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1155 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1156 [NSNumber numberWithUnsignedInteger:10], @"retries",
1157 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1158 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1159 uploadNetworkQueue, @"networkQueue",
1160 @"upload", @"operationType",
1162 if (destinationNode.sharingAccount)
1163 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1164 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1168 [uploadQueue addOperation:operation];
1170 // Upload directory, confirm first
1171 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1172 [alert setMessageText:@"Upload directory"];
1173 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1174 [alert addButtonWithTitle:@"OK"];
1175 [alert addButtonWithTitle:@"Cancel"];
1176 NSInteger choice = [alert runModal];
1177 if (choice == NSAlertFirstButtonReturn) {
1178 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1179 precomposedStringWithCanonicalMapping];
1180 // Operation: Upload a local directory and its descendants
1181 // The resulting ASIPithosObjectRequests are chained through dependencies
1182 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1183 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1184 if (operation.isCancelled) {
1188 NSMutableArray *objectNames = nil;
1189 NSMutableArray *contentTypes = nil;
1190 NSMutableArray *filePaths = nil;
1191 NSMutableArray *hashesArrays = nil;
1192 NSMutableArray *directoryObjectRequests = nil;
1193 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1194 containerName:containerName
1195 objectName:objectName
1198 forDirectory:filePath
1200 objectNames:&objectNames
1201 contentTypes:&contentTypes
1202 filePaths:&filePaths
1203 hashesArrays:&hashesArrays
1204 directoryObjectRequests:&directoryObjectRequests
1205 sharingAccount:destinationNode.sharingAccount];
1206 if (operation.isCancelled) {
1210 ASIPithosObjectRequest *previousObjectRequest = nil;
1211 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1212 if (operation.isCancelled) {
1216 objectRequest.delegate = self;
1217 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1218 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1219 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1220 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1221 message:messagePrefix];
1222 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1223 [NSDictionary dictionaryWithObjectsAndKeys:
1224 [NSNumber numberWithBool:YES], @"refresh",
1225 activity, @"activity",
1226 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1227 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1228 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1229 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1230 [NSNumber numberWithUnsignedInteger:10], @"retries",
1231 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1232 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1233 uploadNetworkQueue, @"networkQueue",
1234 @"upload", @"operationType",
1236 if (previousObjectRequest)
1237 [objectRequest addDependency:previousObjectRequest];
1238 previousObjectRequest = objectRequest;
1239 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1241 if (!operation.isCancelled && objectRequests) {
1242 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1243 if (operation.isCancelled) {
1247 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1248 objectRequest.delegate = self;
1249 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1250 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1251 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1252 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1253 message:[messagePrefix stringByAppendingString:@" (0%)"]
1254 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1256 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1257 [NSDictionary dictionaryWithObjectsAndKeys:
1258 containerName, @"containerName",
1259 [objectNames objectAtIndex:i], @"objectName",
1260 [contentTypes objectAtIndex:i], @"contentType",
1261 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1262 blockHash, @"blockHash",
1263 [filePaths objectAtIndex:i], @"filePath",
1264 [hashesArrays objectAtIndex:i], @"hashes",
1265 [NSNumber numberWithBool:YES], @"refresh",
1266 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1267 activity, @"activity",
1268 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1269 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1270 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1271 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1272 [NSNumber numberWithUnsignedInteger:10], @"retries",
1273 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1274 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1275 uploadNetworkQueue, @"networkQueue",
1276 @"upload", @"operationType",
1278 if (destinationNode.sharingAccount)
1279 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1280 if (previousObjectRequest)
1281 [objectRequest addDependency:previousObjectRequest];
1282 previousObjectRequest = objectRequest;
1283 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1288 [uploadQueue addOperation:operation];
1296 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1297 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1298 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1300 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1301 NSString *objectNamePrefix;
1302 if ([destinationNode class] == [PithosSubdirNode class])
1303 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1305 objectNamePrefix = [NSString string];
1307 for (PithosNode *node in nodes) {
1308 if (([node class] == [PithosObjectNode class]) ||
1309 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1310 // Operation: Move an object or subdir/ node
1311 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1312 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1313 if (operation.isCancelled) {
1317 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1318 if ([node.pithosObject.name hasSuffix:@"/"])
1319 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1320 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1321 containerName:node.pithosContainer.name
1322 objectName:node.pithosObject.name
1323 destinationContainerName:containerName
1324 destinationObjectName:destinationObjectName
1326 if (!operation.isCancelled && objectRequest) {
1327 objectRequest.delegate = self;
1328 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1329 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1330 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1331 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1332 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1333 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1334 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1335 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1336 message:messagePrefix];
1337 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1338 [NSDictionary dictionaryWithObjectsAndKeys:
1339 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1340 activity, @"activity",
1341 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1342 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1343 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1344 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1345 [NSNumber numberWithUnsignedInteger:10], @"retries",
1346 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1347 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1348 moveNetworkQueue, @"networkQueue",
1349 @"move", @"operationType",
1351 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1355 [moveQueue addOperation:operation];
1356 } else if ([node class] == [PithosSubdirNode class]) {
1357 // Operation: Move a subdir node and its descendants
1358 // The resulting ASIPithosObjectRequests are chained through dependencies
1359 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1360 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1361 if (operation.isCancelled) {
1365 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1366 if (node.pithosObject.subdir)
1367 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1368 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1369 containerName:node.pithosContainer.name
1370 objectName:node.pithosObject.name
1371 destinationContainerName:containerName
1372 destinationObjectName:destinationObjectName
1374 if (!operation.isCancelled && objectRequests) {
1375 ASIPithosObjectRequest *previousObjectRequest = nil;
1376 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1377 if (operation.isCancelled) {
1381 objectRequest.delegate = self;
1382 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1383 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1384 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1385 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1386 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1387 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1388 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1389 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1390 message:messagePrefix];
1391 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1392 [NSDictionary dictionaryWithObjectsAndKeys:
1393 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1394 [NSNumber numberWithBool:YES], @"refresh",
1395 activity, @"activity",
1396 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1397 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1398 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1399 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1400 [NSNumber numberWithUnsignedInteger:10], @"retries",
1401 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1402 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1403 moveNetworkQueue, @"networkQueue",
1404 @"move", @"operationType",
1406 if (previousObjectRequest)
1407 [objectRequest addDependency:previousObjectRequest];
1408 previousObjectRequest = objectRequest;
1409 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1414 [moveQueue addOperation:operation];
1420 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1421 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1422 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1424 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1425 NSString *objectNamePrefix;
1426 if ([destinationNode class] == [PithosSubdirNode class])
1427 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1429 objectNamePrefix = [NSString string];
1431 for (PithosNode *node in nodes) {
1432 if (([node class] == [PithosObjectNode class]) ||
1433 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1434 // Operation: Copy an object or subdir/ node
1435 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1436 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1437 if (operation.isCancelled) {
1441 NSString *destinationObjectName;
1442 if (![destinationNode isEqualTo:node.parent]) {
1443 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1444 if ([node.pithosObject.name hasSuffix:@"/"])
1445 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1447 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1448 containerName:containerName
1449 objectName:node.pithosObject.name];
1451 if (operation.isCancelled) {
1455 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos
1456 containerName:node.pithosContainer.name
1457 objectName:node.pithosObject.name
1458 destinationContainerName:containerName
1459 destinationObjectName:destinationObjectName
1461 sharingAccount:node.sharingAccount];
1462 if (!operation.isCancelled && objectRequest) {
1463 objectRequest.delegate = self;
1464 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1465 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1466 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1467 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1468 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1469 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1470 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1471 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1472 message:messagePrefix];
1473 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1474 [NSDictionary dictionaryWithObjectsAndKeys:
1475 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1476 activity, @"activity",
1477 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1478 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1479 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1480 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1481 [NSNumber numberWithUnsignedInteger:10], @"retries",
1482 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1483 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1484 copyNetworkQueue, @"networkQueue",
1485 @"copy", @"operationType",
1487 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1491 [copyQueue addOperation:operation];
1492 } else if ([node class] == [PithosSubdirNode class]) {
1493 // Operation: Copy a subdir node and its descendants
1494 // The resulting ASIPithosObjectRequests are chained through dependencies
1495 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1496 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1497 if (operation.isCancelled) {
1501 NSString *destinationObjectName;
1502 if (![destinationNode isEqualTo:node.parent]) {
1503 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1504 if (node.pithosObject.subdir)
1505 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1507 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1508 containerName:containerName
1509 subdirName:node.pithosObject.name];
1511 if (operation.isCancelled) {
1515 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos
1516 containerName:node.pithosContainer.name
1517 objectName:node.pithosObject.name
1518 destinationContainerName:containerName
1519 destinationObjectName:destinationObjectName
1521 sharingAccount:node.sharingAccount];
1522 if (!operation.isCancelled && objectRequests) {
1523 ASIPithosObjectRequest *previousObjectRequest = nil;
1524 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1525 if (operation.isCancelled) {
1529 objectRequest.delegate = self;
1530 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1531 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1532 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1533 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1534 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1535 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1536 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1537 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1538 message:messagePrefix];
1539 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1540 [NSDictionary dictionaryWithObjectsAndKeys:
1541 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1542 activity, @"activity",
1543 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1544 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1545 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1546 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1547 [NSNumber numberWithUnsignedInteger:10], @"retries",
1548 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1549 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1550 copyNetworkQueue, @"networkQueue",
1551 @"copy", @"operationType",
1553 if (previousObjectRequest)
1554 [objectRequest addDependency:previousObjectRequest];
1555 previousObjectRequest = objectRequest;
1556 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1561 [copyQueue addOperation:operation];
1568 #pragma mark ASIHTTPRequestDelegate
1570 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1571 NSOperationQueue *callbackQueue;
1572 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1573 if ([operationType isEqualToString:@"move"])
1574 callbackQueue = moveCallbackQueue;
1575 else if ([operationType isEqualToString:@"copy"])
1576 callbackQueue = copyCallbackQueue;
1577 else if ([operationType isEqualToString:@"delete"])
1578 callbackQueue = deleteCallbackQueue;
1579 else if ([operationType isEqualToString:@"upload"])
1580 callbackQueue = uploadCallbackQueue;
1581 else if ([operationType isEqualToString:@"download"])
1582 callbackQueue = downloadCallbackQueue;
1584 dispatch_async(dispatch_get_main_queue(), ^{
1585 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1586 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1590 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1591 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1592 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1593 object:request] autorelease];
1594 operation.completionBlock = ^{
1595 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1596 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1597 dispatch_async(dispatch_get_main_queue(), ^{
1598 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1599 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1604 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1605 [callbackQueue addOperation:operation];
1608 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1609 if (request.isCancelled) {
1610 // Request has been cancelled
1611 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1612 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1613 withObject:request];
1615 NSOperationQueue *callbackQueue;
1616 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1617 if ([operationType isEqualToString:@"move"])
1618 callbackQueue = moveCallbackQueue;
1619 else if ([operationType isEqualToString:@"copy"])
1620 callbackQueue = copyCallbackQueue;
1621 else if ([operationType isEqualToString:@"delete"])
1622 callbackQueue = deleteCallbackQueue;
1623 else if ([operationType isEqualToString:@"upload"])
1624 callbackQueue = uploadCallbackQueue;
1625 else if ([operationType isEqualToString:@"download"])
1626 callbackQueue = downloadCallbackQueue;
1628 dispatch_async(dispatch_get_main_queue(), ^{
1629 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1630 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1634 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1635 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1636 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1637 object:request] autorelease];
1638 operation.completionBlock = ^{
1639 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1640 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1641 dispatch_async(dispatch_get_main_queue(), ^{
1642 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1643 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1648 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1649 [callbackQueue addOperation:operation];
1653 - (void)requestFailed:(ASIPithosRequest *)request {
1654 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1655 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1656 NSLog(@"Request failed: %@", request.url);
1657 if (operation.isCancelled) {
1661 if (request.isCancelled) {
1662 dispatch_async(dispatch_get_main_queue(), ^{
1663 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1664 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1669 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1671 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1672 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1673 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1674 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1676 dispatch_async(dispatch_get_main_queue(), ^{
1677 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1678 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1679 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1680 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1682 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1688 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1689 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1690 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1691 NSLog(@"Download finished: %@", objectRequest.url);
1692 if (operation.isCancelled) {
1693 [self requestFailed:objectRequest];
1694 } else if (objectRequest.responseStatusCode == 200) {
1695 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1696 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1697 NSUInteger totalBytes = activity.totalBytes;
1699 // XXX change contentLength to objectContentLength if it is fixed in the server
1700 if ([objectRequest contentLength] == 0) {
1701 // The check above was:
1702 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1703 // I checked for directory content types in order not to create a file in place of a directory,
1704 // but this callback method is not called in the case of a directory download.
1705 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1706 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1707 NSLog(@"Downloaded 0 bytes");
1708 NSFileManager *fileManager = [NSFileManager defaultManager];
1709 if (![fileManager fileExistsAtPath:filePath]) {
1710 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1711 dispatch_async(dispatch_get_main_queue(), ^{
1712 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1713 [alert setMessageText:@"Create File Error"];
1714 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1715 [alert addButtonWithTitle:@"OK"];
1722 NSUInteger currentBytes = [objectRequest objectContentLength];
1723 if (currentBytes == 0)
1724 currentBytes = totalBytes;
1725 dispatch_async(dispatch_get_main_queue(), ^{
1726 [activityFacility endActivity:activity
1727 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1728 totalBytes:totalBytes
1729 currentBytes:currentBytes];
1732 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1733 [self requestFailed:objectRequest];
1738 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1739 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1740 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1741 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1742 if (operation.isCancelled) {
1743 [self requestFailed:objectRequest];
1744 } else if (objectRequest.responseStatusCode == 201) {
1745 NSLog(@"Directory object created: %@", objectRequest.url);
1746 dispatch_async(dispatch_get_main_queue(), ^{
1747 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1748 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1749 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1750 [node forceRefresh];
1752 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1755 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1756 [self forceRefresh:self];
1757 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1758 [self refresh:self];
1761 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1762 [self requestFailed:objectRequest];
1767 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1768 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1769 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1770 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1771 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1772 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1773 NSUInteger totalBytes = activity.totalBytes;
1774 NSUInteger currentBytes = activity.currentBytes;
1775 if (operation.isCancelled) {
1776 [self requestFailed:objectRequest];
1777 } else if (objectRequest.responseStatusCode == 201) {
1778 NSLog(@"Object created: %@", objectRequest.url);
1779 dispatch_async(dispatch_get_main_queue(), ^{
1780 [activityFacility endActivity:activity
1781 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1782 totalBytes:totalBytes
1783 currentBytes:totalBytes];
1784 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1785 [node forceRefresh];
1787 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1790 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1791 [self forceRefresh:self];
1792 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1793 [self refresh:self];
1795 } else if (objectRequest.responseStatusCode == 409) {
1796 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1797 if (iteration == 0) {
1798 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1799 dispatch_async(dispatch_get_main_queue(), ^{
1800 [activityFacility endActivity:activity
1801 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1802 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1803 [alert setMessageText:@"Upload Timeout"];
1804 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1805 [objectRequest.userInfo objectForKey:@"objectName"]]];
1806 [alert addButtonWithTitle:@"OK"];
1812 NSLog(@"object is missing hashes: %@", objectRequest.url);
1813 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1814 withMissingHashes:[objectRequest hashes]];
1815 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1816 if (totalBytes >= [missingBlocks count]*blockSize)
1817 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1818 dispatch_async(dispatch_get_main_queue(), ^{
1819 [activityFacility updateActivity:activity
1820 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1821 totalBytes:totalBytes
1822 currentBytes:currentBytes];
1824 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1825 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1826 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1828 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1829 missingBlockIndex:missingBlockIndex
1830 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1831 newContainerRequest.delegate = self;
1832 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1833 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1834 newContainerRequest.userInfo = objectRequest.userInfo;
1835 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1836 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1837 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1838 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1839 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1840 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1841 [activityFacility updateActivity:activity
1842 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1843 totalBytes:activity.totalBytes
1844 currentBytes:(activity.currentBytes + size)];
1846 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1848 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1849 [self requestFailed:objectRequest];
1854 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1855 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1856 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1857 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1858 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1859 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1860 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1861 if (operation.isCancelled) {
1862 [self requestFailed:containerRequest];
1863 } else if (containerRequest.responseStatusCode == 202) {
1864 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1865 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1866 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1867 if (missingBlockIndex == NSNotFound) {
1868 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1869 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1870 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1871 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1872 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1874 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1875 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1878 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1879 newObjectRequest.delegate = self;
1880 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1881 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1882 newObjectRequest.userInfo = containerRequest.userInfo;
1883 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1884 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1885 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1886 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1887 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1889 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1890 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1891 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1892 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1893 missingBlockIndex:missingBlockIndex
1894 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1895 newContainerRequest.delegate = self;
1896 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1897 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1898 newContainerRequest.userInfo = containerRequest.userInfo;
1899 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1900 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1901 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1902 [activityFacility updateActivity:activity
1903 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1904 totalBytes:activity.totalBytes
1905 currentBytes:(activity.currentBytes + size)];
1907 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1910 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1911 [self requestFailed:containerRequest];
1916 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1917 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1918 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1919 NSLog(@"Move object finished: %@", objectRequest.url);
1920 if (operation.isCancelled) {
1921 [self requestFailed:objectRequest];
1922 } else if (objectRequest.responseStatusCode == 201) {
1923 dispatch_async(dispatch_get_main_queue(), ^{
1924 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1925 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1926 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1927 [node forceRefresh];
1929 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1932 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1933 [self forceRefresh:self];
1934 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1935 [self refresh:self];
1938 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1939 [self requestFailed:objectRequest];
1944 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1945 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1946 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1947 NSLog(@"Copy object finished: %@", objectRequest.url);
1948 if (operation.isCancelled) {
1949 [self requestFailed:objectRequest];
1950 } else if (objectRequest.responseStatusCode == 201) {
1951 dispatch_async(dispatch_get_main_queue(), ^{
1952 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1953 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1954 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1955 [node forceRefresh];
1957 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1960 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1961 [self forceRefresh:self];
1962 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1963 [self refresh:self];
1966 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1967 [self requestFailed:objectRequest];
1972 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1973 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1974 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1975 NSLog(@"Delete object finished: %@", objectRequest.url);
1976 if (operation.isCancelled) {
1977 [self requestFailed:objectRequest];
1978 } else if (objectRequest.responseStatusCode == 204) {
1979 dispatch_async(dispatch_get_main_queue(), ^{
1980 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1981 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1982 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1983 [node forceRefresh];
1985 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1988 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1989 [self forceRefresh:self];
1990 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1991 [self refresh:self];
1994 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1995 [self requestFailed:objectRequest];
2001 #pragma mark NSSplitViewDelegate
2003 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
2004 if (splitView == verticalSplitView)
2007 return ([horizontalSplitView bounds].size.height - 108);
2010 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
2011 if (splitView == verticalSplitView)
2014 return ([horizontalSplitView bounds].size.height - 108);
2017 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2018 if (splitView == verticalSplitView) {
2019 if (proposedPosition < 120)
2021 else if (proposedPosition > 220)
2024 return proposedPosition;
2026 return ([horizontalSplitView bounds].size.height - 108);
2031 #pragma mark NSOutlineViewDataSource
2033 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2034 if (!browserInitialized)
2038 if (item == containersNode)
2039 return containersNodeChildren.count;
2040 if (item == sharedNode)
2045 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2046 if (!browserInitialized)
2049 return (!index ? containersNode : sharedNode);
2050 if (item == sharedNode)
2051 return (!index ? mySharedNode : othersSharedNode);
2052 return [containersNodeChildren objectAtIndex:index];
2055 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2056 if ((item == containersNode) || (item == sharedNode))
2061 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2062 PithosNode *node = (PithosNode *)item;
2066 #pragma mark Drag and Drop destination
2068 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2069 validateDrop:(id<NSDraggingInfo>)info
2070 proposedItem:(id)item
2071 proposedChildIndex:(NSInteger)index {
2072 NSDragOperation result = NSDragOperationNone;
2073 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2075 PithosNode *dropNode = (PithosNode *)item;
2076 if ([dropNode class] != [PithosContainerNode class])
2078 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2079 result = NSDragOperationCopy;
2080 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2081 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2082 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2083 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2084 if (![dropNode isEqualTo:draggedParentNode])
2085 result = NSDragOperationMove;
2086 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2087 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2088 result = NSDragOperationCopy;
2094 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2095 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2096 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2097 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2098 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2099 PithosNode *node = (PithosNode *)item;
2100 NSLog(@"drag in node: %@", node.url);
2101 return [self uploadFiles:filenames toNode:node];
2103 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2104 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2105 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2106 PithosNode *node = (PithosNode *)item;
2107 NSLog(@"drag local node: %@", node.url);
2108 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2109 ([info draggingSourceOperationMask] & NSDragOperationMove))
2110 return [self moveNodes:draggedNodes toNode:node];
2111 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2112 return [self copyNodes:draggedNodes toNode:node];
2119 #pragma mark NSOutlineViewDelegate
2121 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2122 if ((item == containersNode) || (item == sharedNode))
2127 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2128 if ((item == containersNode) || (item == sharedNode))
2133 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2134 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2137 [browser loadColumnZero];
2143 #pragma mark NSMenuDelegate
2145 - (void)menuNeedsUpdate:(NSMenu *)menu {
2146 [menu removeAllItems];
2147 NSMenuItem *menuItem;
2148 NSString *menuItemTitle;
2149 BOOL nodeContextMenu = NO;
2150 PithosNode *menuNode = nil;
2151 NSMutableArray *menuNodes;
2152 if (menu == browserMenu) {
2153 NSInteger column = [browser clickedColumn];
2154 NSInteger row = [browser clickedRow];
2155 if ((column == -1) || (row == -1)) {
2156 // General context menu
2157 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2158 if ([menuNodesIndexPaths count] == 0) {
2159 menuNode = [browser parentForItemsInColumn:0];
2160 } else if (([menuNodesIndexPaths count] != 1) ||
2161 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2162 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2164 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2167 // Node context menu
2168 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2169 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2170 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2171 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2172 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2173 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2176 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2178 nodeContextMenu = YES;
2180 } else if (menu == outlineViewMenu) {
2181 NSInteger row = [outlineView clickedRow];
2183 row = [outlineView selectedRow];
2186 menuNode = [outlineView itemAtRow:row];
2189 if (!nodeContextMenu) {
2190 // General context menu
2191 if (([menuNode class] == [PithosAccountNode class]) ||
2192 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2193 ([menuNode class] == [PithosEmptyNode class]))
2196 if (!menuNode.shared && !menuNode.sharingAccount) {
2197 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2198 [menuItem setRepresentedObject:menuNode];
2199 [menu addItem:menuItem];
2200 [menu addItem:[NSMenuItem separatorItem]];
2203 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2204 [menu addItem:menuItem];
2205 [menu addItem:[NSMenuItem separatorItem]];
2207 menuItem = [[[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2208 action:@selector(menuGetInfo:)
2209 keyEquivalent:@""] autorelease];
2210 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2211 [menu addItem:menuItem];
2213 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2214 (([menuNode class] == [PithosContainerNode class]) ||
2215 (([menuNode class] == [PithosSubdirNode class]) &&
2216 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2217 NSUInteger clipboardNodesCount = [clipboardNodes count];
2218 if (clipboardNodesCount == 0) {
2219 self.clipboardNodes = nil;
2220 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2221 if (clipboardNodesCount == 1)
2222 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2224 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2225 [menu addItem:[NSMenuItem separatorItem]];
2226 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2227 [menuItem setRepresentedObject:menuNode];
2228 [menu addItem:menuItem];
2232 // Node context menu
2233 NSUInteger menuNodesCount = [menuNodes count];
2234 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2236 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2237 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""] autorelease];
2238 [menuItem setRepresentedObject:menuNodes];
2239 [menu addItem:menuItem];
2240 [menu addItem:[NSMenuItem separatorItem]];
2242 // Move to Trash (pithos container only)
2244 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2245 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2246 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2247 action:@selector(menuMoveToTrash:)
2248 keyEquivalent:@""] autorelease];
2249 [menuItem setRepresentedObject:menuNodes];
2250 [menu addItem:menuItem];
2252 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2253 [menuItem setRepresentedObject:menuNodes];
2254 [menu addItem:menuItem];
2255 [menu addItem:[NSMenuItem separatorItem]];
2258 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2259 [menu addItem:menuItem];
2261 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2262 [menu addItem:[NSMenuItem separatorItem]];
2263 menuItem = [[[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2264 action:@selector(menuGetInfo:)
2265 keyEquivalent:@""] autorelease];
2266 [menuItem setRepresentedObject:menuNodes];
2267 [menu addItem:menuItem];
2269 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2270 [menu addItem:[NSMenuItem separatorItem]];
2273 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2274 if (menuNodesCount == 1)
2275 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2277 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2278 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2279 [menuItem setRepresentedObject:menuNodes];
2280 [menu addItem:menuItem];
2283 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2284 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2285 if (menuNodesCount == 1)
2286 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2288 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2289 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2290 [menuItem setRepresentedObject:menuNodes];
2291 [menu addItem:menuItem];
2294 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2295 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2296 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2297 NSUInteger clipboardNodesCount = [clipboardNodes count];
2298 if (clipboardNodesCount == 0) {
2299 self.clipboardNodes = nil;
2300 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2301 if (clipboardNodesCount == 1)
2302 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2304 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2305 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2306 [menuItem setRepresentedObject:firstMenuNode];
2307 [menu addItem:menuItem];
2314 #pragma mark NSMenuValidation
2316 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2317 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2318 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2319 if ([menuNodesIndexPaths count] == 0)
2322 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2323 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2324 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2325 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2326 ((menuItem.action == @selector(delete:)) &&
2327 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2328 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2331 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2332 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2333 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2335 menuItem.representedObject = menuNodes;
2336 } else if (menuItem.action == @selector(paste:)) {
2337 if (!clipboardNodes || ![clipboardNodes count])
2340 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2341 PithosNode *menuNode;
2342 if ([menuNodesIndexPaths count] == 0)
2343 menuNode = [browser parentForItemsInColumn:0];
2344 else if (([menuNodesIndexPaths count] != 1) ||
2345 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2346 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2348 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2350 if (menuNode.shared || menuNode.sharingAccount ||
2351 (([menuNode class] != [PithosContainerNode class]) &&
2352 (([menuNode class] != [PithosSubdirNode class]) ||
2353 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2354 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2357 menuItem.representedObject = menuNode;
2362 - (void)cut:(NSMenuItem *)sender {
2363 [self menuCut:sender];
2366 - (void)copy:(NSMenuItem *)sender {
2367 [self menuCopy:sender];
2370 - (void)paste:(NSMenuItem *)sender {
2371 [self menuPaste:sender];
2374 - (void)delete:(NSMenuItem *)sender {
2375 if (sender.tag == 0)
2376 [self menuMoveToTrash:sender];
2378 [self menuDelete:sender];
2382 #pragma mark Menu Actions
2384 - (void)menuNewFolder:(NSMenuItem *)sender {
2385 PithosNode *node = (PithosNode *)[sender representedObject];
2386 if ([node class] == [PithosContainerNode class]) {
2387 // Operation: Create (upload) a new root application/directory object
2388 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2389 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2390 if (operation.isCancelled) {
2394 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2395 containerName:node.pithosContainer.name
2396 subdirName:@"untitled folder"];
2397 NSString *fileName = [safeObjectName lastPathComponent];
2398 if (operation.isCancelled) {
2402 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2403 containerName:node.pithosContainer.name
2404 objectName:safeObjectName
2406 contentType:@"application/directory"
2408 contentDisposition:nil
2411 isPublic:ASIPithosObjectRequestPublicIgnore
2413 data:[NSData data]];
2414 objectRequest.delegate = self;
2415 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2416 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2417 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2418 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2419 message:messagePrefix];
2420 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2421 fileName, @"fileName",
2422 [NSArray arrayWithObject:node], @"refreshNodes",
2423 [NSNumber numberWithBool:YES], @"refresh",
2424 activity, @"activity",
2425 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2426 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2427 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2428 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2429 [NSNumber numberWithUnsignedInteger:10], @"retries",
2430 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2431 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2432 uploadNetworkQueue, @"networkQueue",
2433 @"upload", @"operationType",
2435 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2438 [uploadQueue addOperation:operation];
2439 } else if (([node class] == [PithosSubdirNode class]) &&
2440 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2441 // Operation: Create (upload) a new aplication/directory object
2442 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2443 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2444 if (operation.isCancelled) {
2448 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2449 containerName:node.pithosContainer.name
2450 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2451 NSString *fileName = [safeObjectName lastPathComponent];
2452 if (operation.isCancelled) {
2456 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2457 containerName:node.pithosContainer.name
2458 objectName:safeObjectName
2460 contentType:@"application/directory"
2462 contentDisposition:nil
2465 isPublic:ASIPithosObjectRequestPublicIgnore
2467 data:[NSData data]];
2468 objectRequest.delegate = self;
2469 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2470 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2471 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2472 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2473 message:messagePrefix];
2474 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2475 fileName, @"fileName",
2476 [NSArray arrayWithObject:node], @"refreshNodes",
2477 [NSNumber numberWithBool:YES], @"refresh",
2478 activity, @"activity",
2479 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2480 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2481 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2482 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2483 [NSNumber numberWithUnsignedInteger:10], @"retries",
2484 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2485 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2486 uploadNetworkQueue, @"networkQueue",
2487 @"upload", @"operationType",
2489 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2492 [uploadQueue addOperation:operation];
2496 - (void)menuGetInfo:(NSMenuItem *)sender {
2497 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2498 [node showPithosNodeInfo:sender];
2502 - (void)menuDownload:(NSMenuItem *)sender {
2503 NSArray *nodes = (NSArray *)[sender representedObject];
2504 PithosNode *firstNode = [nodes objectAtIndex:0];
2505 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2506 NSSavePanel *save = [NSSavePanel savePanel];
2507 save.nameFieldStringValue = firstNode.displayName;
2508 int result = [save runModal];
2509 if (result == NSOKButton) {
2510 NSString *destinationPath = save.URL.path;
2511 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2512 NSString *newFileName = [destinationPath lastPathComponent];
2513 if ([destinationPath hasSuffix:@"/"])
2514 newFileName = [newFileName stringByAppendingString:@"/"];
2515 if ([firstNode.displayName isEqualToString:newFileName])
2517 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2520 NSOpenPanel *open = [NSOpenPanel openPanel];
2521 open.canChooseFiles = NO;
2522 open.canChooseDirectories = YES;
2523 open.canCreateDirectories = YES;
2524 int result = [open runModal];
2525 if (result == NSOKButton) {
2526 NSString *directoryPath = open.URL.path;
2527 for (PithosNode *node in nodes) {
2528 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2534 - (void)menuDelete:(NSMenuItem *)sender {
2535 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2536 if (([node class] == [PithosObjectNode class]) ||
2537 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2538 // Operation: Delete an object or subdir/ node
2539 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2540 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2541 if (operation.isCancelled) {
2545 NSString *fileName = [node.pithosObject.name lastPathComponent];
2546 if ([node.pithosObject.name hasSuffix:@"/"])
2547 fileName = [fileName stringByAppendingString:@"/"];
2548 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2549 containerName:node.pithosContainer.name
2550 objectName:node.pithosObject.name];
2551 if (operation.isCancelled) {
2555 objectRequest.delegate = self;
2556 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2557 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2558 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2559 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2560 message:messagePrefix];
2561 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2562 fileName, @"fileName",
2563 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2564 activity, @"activity",
2565 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2566 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2567 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2568 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2569 [NSNumber numberWithUnsignedInteger:10], @"retries",
2570 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2571 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2572 deleteNetworkQueue, @"networkQueue",
2573 @"delete", @"operationType",
2575 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2578 [deleteQueue addOperation:operation];
2579 } else if ([node class] == [PithosSubdirNode class]) {
2580 // Operation: Delete a subdir node and its descendants
2581 // The resulting ASIPithosObjectRequests are chained through dependencies
2582 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2583 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2584 if (operation.isCancelled) {
2588 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2589 containerName:node.pithosContainer.name
2590 objectName:node.pithosObject.name];
2591 if (!operation.isCancelled && objectRequests) {
2592 ASIPithosObjectRequest *previousObjectRequest = nil;
2593 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2594 if (operation.isCancelled) {
2598 objectRequest.delegate = self;
2599 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2600 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2601 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2602 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2603 message:messagePrefix];
2604 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2605 [NSDictionary dictionaryWithObjectsAndKeys:
2606 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2607 activity, @"activity",
2608 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2609 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2610 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2611 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2612 [NSNumber numberWithUnsignedInteger:10], @"retries",
2613 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2614 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2615 deleteNetworkQueue, @"networkQueue",
2616 @"delete", @"operationType",
2618 if (previousObjectRequest)
2619 [objectRequest addDependency:previousObjectRequest];
2620 previousObjectRequest = objectRequest;
2621 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2626 [deleteQueue addOperation:operation];
2631 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2632 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2633 if (([node class] == [PithosObjectNode class]) ||
2634 (([node class] == [PithosSubdirNode class]) &&
2635 !node.pithosObject.subdir &&
2636 [node.pithosObject.name hasSuffix:@"/"])) {
2637 // Operation: Move to trash an object or subdir/ node
2638 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2639 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2640 if (operation.isCancelled) {
2644 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2645 containerName:@"trash"
2646 objectName:node.pithosObject.name];
2647 if (!operation.isCancelled && safeObjectName) {
2648 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2649 containerName:node.pithosContainer.name
2650 objectName:node.pithosObject.name
2651 destinationContainerName:@"trash"
2652 destinationObjectName:safeObjectName
2654 if (!operation.isCancelled && objectRequest) {
2655 objectRequest.delegate = self;
2656 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2657 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2658 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2659 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2660 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2661 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2662 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2663 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2664 message:messagePrefix];
2665 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2666 [NSDictionary dictionaryWithObjectsAndKeys:
2667 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2668 activity, @"activity",
2669 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2670 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2671 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2672 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2673 [NSNumber numberWithUnsignedInteger:10], @"retries",
2674 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2675 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2676 moveNetworkQueue, @"networkQueue",
2677 @"move", @"operationType",
2679 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2684 [moveQueue addOperation:operation];
2685 } else if ([node class] == [PithosSubdirNode class]) {
2686 // Operation: Move to trash a subdir node and its descendants
2687 // The resulting ASIPithosObjectRequests are chained through dependencies
2688 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2689 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2690 if (operation.isCancelled) {
2694 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2695 containerName:@"trash"
2696 subdirName:node.pithosObject.name];
2697 if (!operation.isCancelled && safeObjectName) {
2698 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2699 containerName:node.pithosContainer.name
2700 objectName:node.pithosObject.name
2701 destinationContainerName:@"trash"
2702 destinationObjectName:safeObjectName
2704 if (!operation.isCancelled && objectRequests) {
2705 ASIPithosObjectRequest *previousObjectRequest = nil;
2706 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2707 if (operation.isCancelled) {
2711 objectRequest.delegate = self;
2712 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2713 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2714 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2715 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2716 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2717 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2718 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2719 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2720 message:messagePrefix];
2721 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2722 [NSDictionary dictionaryWithObjectsAndKeys:
2723 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2724 activity, @"activity",
2725 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2726 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2727 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2728 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2729 [NSNumber numberWithUnsignedInteger:10], @"retries",
2730 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2731 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2732 moveNetworkQueue, @"networkQueue",
2733 @"move", @"operationType",
2735 if (previousObjectRequest)
2736 [objectRequest addDependency:previousObjectRequest];
2737 previousObjectRequest = objectRequest;
2738 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2744 [moveQueue addOperation:operation];
2749 - (void)menuCut:(NSMenuItem *)sender {
2750 self.clipboardNodes = (NSArray *)[sender representedObject];
2751 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2752 self.clipboardCopy = NO;
2755 - (void)menuCopy:(NSMenuItem *)sender {
2756 self.clipboardNodes = (NSArray *)[sender representedObject];
2757 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2758 self.clipboardCopy = YES;
2761 - (void)menuPaste:(NSMenuItem *)sender {
2762 if (!clipboardNodes || ![clipboardNodes count])
2764 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2765 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2766 if (clipboardCopy) {
2767 [self copyNodes:localClipboardNodes toNode:dropNode];
2768 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2769 self.clipboardNodes = nil;
2770 self.clipboardParentNode = nil;
2771 [self moveNodes:localClipboardNodes toNode:dropNode];
2776 #pragma mark PithosActivityFacilityDelegate
2778 - (void)activityUpdate:(NSDictionary *)info {
2779 NSString *message = [info objectForKey:@"message"];
2780 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2781 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2782 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2783 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2784 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2785 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2786 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2787 if (runningActivitiesCount && totalBytes) {
2788 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2789 [activityProgressIndicator startAnimation:self];
2791 [activityProgressIndicator setDoubleValue:1.0];
2792 [activityProgressIndicator stopAnimation:self];
2796 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2797 [activityTextField setStringValue:message];