2 // PithosBrowserController.m
5 // Copyright 2011-2012 GRNET S.A. All rights reserved.
7 // Redistribution and use in source and binary forms, with or
8 // without modification, are permitted provided that the following
11 // 1. Redistributions of source code must retain the above
12 // copyright notice, this list of conditions and the following
15 // 2. Redistributions in binary form must reproduce the above
16 // copyright notice, this list of conditions and the following
17 // disclaimer in the documentation and/or other materials
18 // provided with the distribution.
20 // THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
21 // OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
24 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
27 // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
28 // AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30 // ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 // POSSIBILITY OF SUCH DAMAGE.
33 // The views and conclusions contained in the software and
34 // documentation are those of the authors and should not be
35 // interpreted as representing official policies, either expressed
36 // or implied, of GRNET S.A.
38 #import "PithosBrowserController.h"
39 #import "PithosNode.h"
40 #import "PithosAccountNode.h"
41 #import "PithosContainerNode.h"
42 #import "PithosSubdirNode.h"
43 #import "PithosObjectNode.h"
44 #import "PithosSharingAccountsNode.h"
45 #import "PithosEmptyNode.h"
46 #import "ImageAndTextCell.h"
47 #import "FileSystemBrowserCell.h"
48 #import "ASINetworkQueue.h"
49 #import "ASIPithosRequest.h"
51 #import "ASIPithosContainerRequest.h"
52 #import "ASIPithosObjectRequest.h"
53 #import "ASIPithosAccount.h"
54 #import "ASIPithosContainer.h"
55 #import "ASIPithosObject.h"
56 #import "PithosUtilities.h"
57 #import "UsingSizeTransformer.h"
59 #define REFRESH_TIMER_INTERVAL 5
61 @interface PithosBrowserCell : FileSystemBrowserCell {}
64 @implementation PithosBrowserCell
67 if ((self = [super init])) {
68 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
69 [self setEditable:YES];
74 - (void)setObjectValue:(id)object {
75 if ([object isKindOfClass:[PithosNode class]]) {
76 PithosNode *node = (PithosNode *)object;
77 [self setStringValue:node.displayName];
78 [self setImage:node.icon];
80 [super setObjectValue:object];
86 @interface PithosOutlineViewCell : ImageAndTextCell {}
89 @implementation PithosOutlineViewCell
91 - (void)setObjectValue:(id)object {
92 if ([object isKindOfClass:[PithosNode class]]) {
93 PithosNode *node = (PithosNode *)object;
94 [self setStringValue:node.displayName];
95 [self setImage:node.icon];
96 [self setEditable:NO];
98 [super setObjectValue:object];
104 @interface PithosBrowserController (Private)
105 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
106 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
107 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
110 @implementation PithosBrowserController
112 @synthesize accountNode;
113 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu;
114 @synthesize draggedNodes, draggedParentNode;
115 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
116 @synthesize activityTextField, activityProgressIndicator;
119 #pragma Object Lifecycle
122 return [super initWithWindowNibName:@"PithosBrowserController"];
125 - (void)windowDidLoad {
126 [super windowDidLoad];
127 if (browser && !browserInitialized) {
128 browserInitialized = YES;
133 - (void)initBrowser {
134 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
135 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
136 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
138 [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
139 [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
140 [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
142 [browser setCellClass:[PithosBrowserCell class]];
143 [browser setAllowsBranchSelection:YES];
144 [browser setAllowsMultipleSelection:YES];
145 [browser setAllowsEmptySelection:YES];
146 [browser setAllowsTypeSelect:YES];
147 [browser setDoubleAction:@selector(browserDoubleAction:)];
149 moveNetworkQueue = [[ASINetworkQueue alloc] init];
150 moveNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
151 // moveNetworkQueue.maxConcurrentOperationCount = 1;
152 copyNetworkQueue = [[ASINetworkQueue alloc] init];
153 copyNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
154 // copyNetworkQueue.maxConcurrentOperationCount = 1;
155 deleteNetworkQueue = [[ASINetworkQueue alloc] init];
156 deleteNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
157 // deleteNetworkQueue.maxConcurrentOperationCount = 1;
158 uploadNetworkQueue = [[ASINetworkQueue alloc] init];
159 uploadNetworkQueue.showAccurateProgress = YES;
160 uploadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
161 // uploadNetworkQueue.maxConcurrentOperationCount = 1;
162 downloadNetworkQueue = [[ASINetworkQueue alloc] init];
163 downloadNetworkQueue.showAccurateProgress = YES;
164 downloadNetworkQueue.shouldCancelAllRequestsOnFailure = NO;
165 // downloadNetworkQueue.maxConcurrentOperationCount = 1;
167 moveQueue = [[NSOperationQueue alloc] init];
168 [moveQueue setSuspended:YES];
169 moveQueue.name = @"gr.grnet.pithos.MoveQueue";
170 // moveQueue.maxConcurrentOperationCount = 1;
171 copyQueue = [[NSOperationQueue alloc] init];
172 [copyQueue setSuspended:YES];
173 copyQueue.name = @"gr.grnet.pithos.CopyQueue";
174 // copyQueue.maxConcurrentOperationCount = 1;
175 deleteQueue = [[NSOperationQueue alloc] init];
176 [deleteQueue setSuspended:YES];
177 deleteQueue.name = @"gr.grnet.pithos.DeleteQueue";
178 // deleteQueue.maxConcurrentOperationCount = 1;
179 uploadQueue = [[NSOperationQueue alloc] init];
180 [uploadQueue setSuspended:YES];
181 uploadQueue.name = @"gr.grnet.pithos.UploadQueue";
182 // uploadQueue.maxConcurrentOperationCount = 1;
183 downloadQueue = [[NSOperationQueue alloc] init];
184 [downloadQueue setSuspended:YES];
185 downloadQueue.name = @"gr.grnet.pithos.DownloadQueue";
186 // downloadQueue.maxConcurrentOperationCount = 1;
188 moveCallbackQueue = [[NSOperationQueue alloc] init];
189 [moveCallbackQueue setSuspended:YES];
190 moveCallbackQueue.name = @"gr.grnet.pithos.MoveCallbackQueue";
191 // moveCallbackQueue.maxConcurrentOperationCount = 1;
192 copyCallbackQueue = [[NSOperationQueue alloc] init];
193 [copyCallbackQueue setSuspended:YES];
194 copyCallbackQueue.name = @"gr.grnet.pithos.CopyCallbackQueue";
195 // copyCallbackQueue.maxConcurrentOperationCount = 1;
196 deleteCallbackQueue = [[NSOperationQueue alloc] init];
197 [deleteCallbackQueue setSuspended:YES];
198 deleteCallbackQueue.name = @"gr.grnet.pithos.DeleteCallbackQueue";
199 // deleteCallbackQueue.maxConcurrentOperationCount = 1;
200 uploadCallbackQueue = [[NSOperationQueue alloc] init];
201 [uploadCallbackQueue setSuspended:YES];
202 uploadCallbackQueue.name = @"gr.grnet.pithos.UploadCallbackQueue";
203 // uploadCallbackQueue.maxConcurrentOperationCount = 1;
204 downloadCallbackQueue = [[NSOperationQueue alloc] init];
205 [downloadCallbackQueue setSuspended:YES];
206 downloadCallbackQueue.name = @"gr.grnet.pithos.DownloadCallbackQueue";
207 // downloadCallbackQueue.maxConcurrentOperationCount = 1;
209 [activityProgressIndicator setUsesThreadedAnimation:YES];
210 [activityProgressIndicator setMinValue:0.0];
211 [activityProgressIndicator setMaxValue:1.0];
212 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
214 self.accountNode = [[[PithosAccountNode alloc] initWithPithos:pithos] autorelease];
215 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
216 containersNodeChildren = [[NSMutableArray alloc] init];
217 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
218 mySharedNode = [[PithosAccountNode alloc] initWithPithos:pithos];
219 mySharedNode.displayName = @"shared by me";
220 mySharedNode.shared = YES;
221 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
222 othersSharedNode = [[PithosSharingAccountsNode alloc] initWithPithos:pithos];
223 othersSharedNode.displayName = @"shared to me";
224 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
226 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
228 // Register for updates
229 // PithosAccountNode accountNode updates outlineView container nodes
230 [[NSNotificationCenter defaultCenter] addObserver:self
231 selector:@selector(pithosAccountNodeChildrenUpdated:)
232 name:@"PithosNodeChildrenUpdated"
234 // PithosNode updates browser nodes
235 [[NSNotificationCenter defaultCenter] addObserver:self
236 selector:@selector(pithosNodeChildrenUpdated:)
237 name:@"PithosNodeChildrenUpdated"
239 // Request for browser refresh
240 [[NSNotificationCenter defaultCenter] addObserver:self
241 selector:@selector(pithosBrowserRefreshNeeded:)
242 name:@"PithosBrowserRefreshNeeeded"
246 - (void)resetBrowser {
247 @synchronized(self) {
252 [refreshTimer invalidate];
253 [refreshTimer release];
255 [moveNetworkQueue reset];
256 [copyNetworkQueue reset];
257 [deleteNetworkQueue reset];
258 [uploadNetworkQueue reset];
259 [downloadNetworkQueue reset];
261 [moveQueue cancelAllOperations];
262 [moveQueue setSuspended:YES];
263 [copyQueue cancelAllOperations];
264 [copyQueue setSuspended:YES];
265 [deleteQueue cancelAllOperations];
266 [deleteQueue setSuspended:YES];
267 [uploadQueue cancelAllOperations];
268 [uploadQueue setSuspended:YES];
269 [downloadQueue cancelAllOperations];
270 [downloadQueue setSuspended:YES];
272 [moveCallbackQueue cancelAllOperations];
273 [moveCallbackQueue setSuspended:YES];
274 [copyCallbackQueue cancelAllOperations];
275 [copyCallbackQueue setSuspended:YES];
276 [deleteCallbackQueue cancelAllOperations];
277 [deleteCallbackQueue setSuspended:YES];
278 [uploadCallbackQueue cancelAllOperations];
279 [uploadCallbackQueue setSuspended:YES];
280 [downloadCallbackQueue cancelAllOperations];
281 [downloadCallbackQueue setSuspended:YES];
284 [browser loadColumnZero];
285 [containersNodeChildren removeAllObjects];
286 [outlineView reloadData];
287 // Expand the folder outline view
288 [outlineView expandItem:nil expandChildren:YES];
289 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
291 activityFacility.delegate = nil;
292 [activityProgressIndicator setDoubleValue:1.0];
293 [activityProgressIndicator stopAnimation:self];
295 @synchronized(self) {
300 - (void)startBrowser {
301 @synchronized(self) {
306 // In the improbable case of leftover operations
307 [moveNetworkQueue reset];
308 [copyNetworkQueue reset];
309 [deleteNetworkQueue reset];
310 [uploadNetworkQueue reset];
311 [downloadNetworkQueue reset];
312 [moveQueue cancelAllOperations];
313 [copyQueue cancelAllOperations];
314 [deleteQueue cancelAllOperations];
315 [uploadQueue cancelAllOperations];
316 [downloadQueue cancelAllOperations];
317 [moveCallbackQueue cancelAllOperations];
318 [copyCallbackQueue cancelAllOperations];
319 [deleteCallbackQueue cancelAllOperations];
320 [uploadCallbackQueue cancelAllOperations];
321 [downloadCallbackQueue cancelAllOperations];
323 [moveNetworkQueue go];
324 [copyNetworkQueue go];
325 [deleteNetworkQueue go];
326 [uploadNetworkQueue go];
327 [downloadNetworkQueue go];
328 [moveQueue setSuspended:NO];
329 [copyQueue setSuspended:NO];
330 [deleteQueue setSuspended:NO];
331 [uploadQueue setSuspended:NO];
332 [downloadQueue setSuspended:NO];
333 [moveCallbackQueue setSuspended:NO];
334 [copyCallbackQueue setSuspended:NO];
335 [deleteCallbackQueue setSuspended:NO];
336 [uploadCallbackQueue setSuspended:NO];
337 [downloadCallbackQueue setSuspended:NO];
339 accountNode.pithos = pithos;
340 [accountNode forceRefresh];
341 mySharedNode.pithos = pithos;
342 [mySharedNode forceRefresh];
343 othersSharedNode.pithos = pithos;
344 [othersSharedNode forceRefresh];
346 // [activityFacility reset];
347 activityFacility.delegate = self;
349 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:REFRESH_TIMER_INTERVAL
351 selector:@selector(forceRefresh:)
353 repeats:YES] retain];
354 @synchronized(self) {
359 - (BOOL)operationsPending {
360 return ([moveNetworkQueue operationCount] ||
361 [copyNetworkQueue operationCount] ||
362 [deleteNetworkQueue operationCount] ||
363 [uploadNetworkQueue operationCount] ||
364 [downloadNetworkQueue operationCount] ||
365 [moveQueue operationCount] ||
366 [copyQueue operationCount] ||
367 [deleteQueue operationCount] ||
368 [uploadQueue operationCount] ||
369 [downloadQueue operationCount] ||
370 [moveCallbackQueue operationCount] ||
371 [copyCallbackQueue operationCount] ||
372 [deleteCallbackQueue operationCount] ||
373 [uploadCallbackQueue operationCount] ||
374 [downloadCallbackQueue operationCount]);
378 [[NSNotificationCenter defaultCenter] removeObserver:self];
382 [deleteQueue release];
383 [uploadQueue release];
384 [downloadQueue release];
385 [moveNetworkQueue release];
386 [copyNetworkQueue release];
387 [deleteNetworkQueue release];
388 [uploadNetworkQueue release];
389 [downloadNetworkQueue release];
390 [clipboardParentNode release];
391 [clipboardNodes release];
392 [draggedParentNode release];
393 [draggedNodes release];
394 [sharedPreviewController release];
395 [othersSharedNode release];
396 [mySharedNode release];
397 [sharedNode release];
398 [containersNodeChildren release];
399 [containersNode release];
400 [accountNode release];
406 - (void)setPithos:(ASIPithos *)aPithos {
408 if (![aPithos.authUser isEqualToString:pithos.authUser] ||
409 ![aPithos.authToken isEqualToString:pithos.authToken] ||
410 ![aPithos.storageURLPrefix isEqualToString:pithos.storageURLPrefix] ||
411 ![aPithos.publicURLPrefix isEqualToString:pithos.publicURLPrefix]) {
414 pithos = [aPithos retain];
424 #pragma mark Observers
426 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
427 PithosNode *node = (PithosNode *)[notification object];
428 if ((node == accountNode) || ![node.pithos isEqualTo:pithos])
430 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
431 NSInteger lastColumn = [browser lastColumn];
432 for (NSInteger column = lastColumn; column >= 0; column--) {
433 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
434 [browser reloadColumn:column];
440 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
441 BOOL containerPithosFound = NO;
442 BOOL containerTrashFound = NO;
443 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
444 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
445 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
446 [removedContainersNodeChildren addIndex:i];
448 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
449 for (PithosContainerNode *containerNode in accountNode.children) {
450 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
451 if (![containersNodeChildren containsObject:containerNode])
452 [containersNodeChildren insertObject:containerNode atIndex:0];
453 containerPithosFound = YES;
454 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
455 NSUInteger insertIndex = 1;
456 if (!containerPithosFound)
458 if (![containersNodeChildren containsObject:containerNode])
459 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
460 containerTrashFound = YES;
461 } else if (![containersNodeChildren containsObject:containerNode]) {
462 [containersNodeChildren addObject:containerNode];
465 BOOL refreshAccountNode = NO;
466 if (!containerPithosFound) {
467 // Create pithos node
468 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
469 containerName:@"pithos"];
470 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
472 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
473 if ([containerRequest error]) {
474 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
476 refreshAccountNode = YES;
479 if (!containerTrashFound) {
481 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithPithos:pithos
482 containerName:@"trash"];
483 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
485 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
486 if ([containerRequest error]) {
487 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
489 refreshAccountNode = YES;
493 if (refreshAccountNode)
494 [accountNode refresh];
496 [outlineView reloadData];
498 // Expand the folder outline view
499 [outlineView expandItem:nil expandChildren:YES];
501 if (((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) && [containersNodeChildren count]) {
502 rootNode = [containersNodeChildren objectAtIndex:0];
503 [browser loadColumnZero];
510 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
517 - (IBAction)forceRefresh:(id)sender {
521 [accountNode forceRefresh];
522 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
523 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
524 node.forcedRefresh = YES;
525 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
527 [browser validateVisibleColumns];
530 - (IBAction)refresh:(id)sender {
533 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
534 [self forceRefresh:sender];
537 [accountNode refresh];
538 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
539 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
541 [browser validateVisibleColumns];
546 #pragma mark NSBrowserDelegate
548 - (id)rootItemForBrowser:(NSBrowser *)browser {
552 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
553 PithosNode *node = (PithosNode *)item;
554 return node.children.count;
557 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
558 PithosNode *node = (PithosNode *)item;
559 return [node.children objectAtIndex:index];
562 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
563 PithosNode *node = (PithosNode *)item;
564 return node.isLeafItem;
567 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
568 PithosNode *node = (PithosNode *)item;
572 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
573 if (sharedPreviewController == nil)
574 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
575 return sharedPreviewController;
578 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
579 // if (!forUserResize) {
580 // id item = [browser parentForItemsInColumn:columnIndex];
581 // if ([self browser:browser isLeafItem:item]) {
582 // suggestedWidth = 200;
585 // return suggestedWidth;
588 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
594 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
595 PithosNode *node = (PithosNode *)item;
596 if (node.shared || node.sharingAccount ||
597 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
603 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
605 PithosNode *node = (PithosNode *)item;
606 NSString *newName = (NSString *)object;
607 NSUInteger newNameLength = [newName length];
608 NSRange firstSlashRange = [newName rangeOfString:@"/"];
609 if ((newNameLength == 0) ||
610 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
611 ([newName isEqualToString:node.displayName])) {
614 if (([node class] == [PithosObjectNode class]) ||
615 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
616 // Operation: Rename (move) an object or subdir/ node
617 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
618 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
619 if (operation.isCancelled) {
623 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
624 if ([newName hasSuffix:@"/"])
625 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
626 NSError *error = nil;
628 if ([PithosUtilities objectExistsAtPithos:pithos
629 containerName:node.pithosContainer.name
630 objectName:destinationObjectName
632 isDirectory:&isDirectory
633 sharingAccount:nil]) {
634 dispatch_async(dispatch_get_main_queue(), ^{
635 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
636 [alert setMessageText:@"Name Taken"];
637 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
638 [alert addButtonWithTitle:@"OK"];
647 if (operation.isCancelled) {
651 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
652 containerName:node.pithosContainer.name
653 objectName:node.pithosObject.name
654 destinationContainerName:node.pithosContainer.name
655 destinationObjectName:destinationObjectName
657 if (!operation.isCancelled && objectRequest) {
658 objectRequest.delegate = self;
659 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
660 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
661 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
662 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
663 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
664 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
665 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
666 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
667 message:messagePrefix];
668 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
669 [NSDictionary dictionaryWithObjectsAndKeys:
670 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
671 [NSNumber numberWithBool:YES], @"refresh",
672 activity, @"activity",
673 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
674 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
675 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
676 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
677 [NSNumber numberWithUnsignedInteger:10], @"retries",
678 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
679 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
680 moveNetworkQueue, @"networkQueue",
681 @"move", @"operationType",
683 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
687 [moveQueue addOperation:operation];
688 } else if ([node class] == [PithosSubdirNode class]) {
689 if (firstSlashRange.length == 1)
691 // Operation: Rename (move) a subdir node and its descendants
692 // The resulting ASIPithosObjectRequests are chained through dependencies
693 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
694 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
695 if (operation.isCancelled) {
699 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
700 NSError *error = nil;
702 if ([PithosUtilities objectExistsAtPithos:pithos
703 containerName:node.pithosContainer.name
704 objectName:destinationObjectName
706 isDirectory:&isDirectory
707 sharingAccount:nil]) {
708 dispatch_async(dispatch_get_main_queue(), ^{
709 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
710 [alert setMessageText:@"Name Taken"];
711 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
712 [alert addButtonWithTitle:@"OK"];
721 if (operation.isCancelled) {
725 if (node.pithosObject.subdir)
726 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
727 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
728 containerName:node.pithosContainer.name
729 objectName:node.pithosObject.name
730 destinationContainerName:node.pithosContainer.name
731 destinationObjectName:destinationObjectName
733 if (!operation.isCancelled && objectRequests) {
734 ASIPithosObjectRequest *previousObjectRequest = nil;
735 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
736 if (operation.isCancelled) {
740 objectRequest.delegate = self;
741 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
742 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
743 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
744 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
745 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
746 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
747 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
748 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
749 message:messagePrefix];
750 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
751 [NSDictionary dictionaryWithObjectsAndKeys:
752 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
753 [NSNumber numberWithBool:YES], @"refresh",
754 activity, @"activity",
755 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
756 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
757 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
758 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
759 [NSNumber numberWithUnsignedInteger:10], @"retries",
760 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
761 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
762 moveNetworkQueue, @"networkQueue",
763 @"move", @"operationType",
765 if (previousObjectRequest)
766 [objectRequest addDependency:previousObjectRequest];
767 previousObjectRequest = objectRequest;
768 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
773 [moveQueue addOperation:operation];
777 #pragma mark Drag and Drop source
779 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
780 withEvent:(NSEvent *)event {
781 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
782 __block BOOL result = YES;
783 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
784 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
785 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
793 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
794 toPasteboard:(NSPasteboard *)pasteboard {
795 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
796 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
797 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
798 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
799 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
800 [propertyList addObject:[node.pithosObject.name pathExtension]];
801 [nodes addObject:node];
804 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
805 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
806 self.draggedNodes = nodes;
807 self.draggedParentNode = [browser parentForItemsInColumn:column];
811 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
812 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
813 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
814 for (PithosNode *node in draggedNodes) {
815 [names addObject:node.displayName];
816 // If the node is a subdir ask if the whole tree should be downloaded
817 if ([node class] == [PithosSubdirNode class]) {
818 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
819 [alert setMessageText:@"Download directory"];
820 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
821 [alert addButtonWithTitle:@"OK"];
822 [alert addButtonWithTitle:@"Cancel"];
823 NSInteger choice = [alert runModal];
824 if (choice == NSAlertFirstButtonReturn)
825 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
827 [self downloadNode:node toDirectory:[dropDestination path] withNewFileName:nil version:nil checkIfExists:YES];
833 #pragma mark Drag and Drop destination
835 - (NSDragOperation)browser:aBrowser
836 validateDrop:(id<NSDraggingInfo>)info
837 proposedRow:(NSInteger *)row
838 column:(NSInteger *)column
839 dropOperation:(NSBrowserDropOperation *)dropOperation {
840 NSDragOperation result = NSDragOperationNone;
841 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
842 // For a drop above, the drop is redirected to the parent item
843 if (*dropOperation == NSBrowserDropAbove)
845 // Only allow dropping in folders
847 PithosNode *dropNode;
849 // Check if the node is not a folder and if so redirect to the parent item
850 dropNode = [browser itemAtRow:*row inColumn:*column];
851 if ([dropNode class] == [PithosObjectNode class])
855 dropNode = [browser parentForItemsInColumn:*column];
857 if (!dropNode.shared &&
858 (!dropNode.sharingAccount ||
859 ([dropNode class] == [PithosSubdirNode class]) ||
860 ([dropNode class] == [PithosContainerNode class]))) {
861 *dropOperation = NSBrowserDropOn;
862 result = NSDragOperationCopy;
865 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
866 // For a drop above, the drop is redirected to the parent item
867 if (*dropOperation == NSBrowserDropAbove)
869 // Only allow dropping in folders
871 PithosNode *dropNode;
873 // Check if the node is not a folder and if so redirect to the parent item
874 dropNode = [browser itemAtRow:*row inColumn:*column];
875 if ([dropNode class] == [PithosObjectNode class])
879 dropNode = [browser parentForItemsInColumn:*column];
881 if (!dropNode.shared && !dropNode.sharingAccount) {
882 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
883 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
884 if ((([dropNode class] == [PithosContainerNode class]) ||
885 dropNode.pithosObject.subdir ||
886 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
887 ![dropNode isEqualTo:draggedParentNode]) {
888 // ![dropNode isEqualTo:draggedParentNode] &&
889 // ![draggedNodes containsObject:dropNode]) {
890 result = NSDragOperationMove;
892 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
893 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
894 if (([dropNode class] == [PithosContainerNode class]) ||
895 dropNode.pithosObject.subdir ||
896 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
897 result = NSDragOperationCopy;
906 - (BOOL)browser:(NSBrowser *)aBrowser
907 acceptDrop:(id<NSDraggingInfo>)info
909 column:(NSInteger)column
910 dropOperation:(NSBrowserDropOperation)dropOperation {
911 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
912 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
913 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
914 if ((column != -1) && (filenames != nil)) {
917 node = [browser itemAtRow:row inColumn:column];
919 node = [browser parentForItemsInColumn:column];
920 NSLog(@"drag in node: %@", node.url);
921 return [self uploadFiles:filenames toNode:node];
923 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
924 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
925 if ((column != -1) && (draggedNodes != nil)) {
928 node = [browser itemAtRow:row inColumn:column];
930 node = [browser parentForItemsInColumn:column];
931 NSLog(@"drag local node: %@", node.url);
932 if ([info draggingSourceOperationMask] & NSDragOperationMove)
933 return [self moveNodes:draggedNodes toNode:node];
934 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
935 return [self copyNodes:draggedNodes toNode:node];
942 #pragma mark NSBrowser Actions
944 - (void)browserDoubleAction:(id)sender {
945 NSInteger column = [browser clickedColumn];
946 NSInteger row = [browser clickedRow];
947 if ((column == -1) || (row == -1))
949 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
950 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
951 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
952 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
953 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
954 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
957 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
959 NSMenuItem *menuItem = [[[NSMenuItem alloc] init] autorelease];
960 menuItem.representedObject = menuNodes;
961 [self menuDownload:menuItem];
965 #pragma mark Drag and Drop methods
967 - (void)downloadNode:(PithosNode *)node toDirectory:(NSString *)dirPath withNewFileName:(NSString *)newFileName
968 version:(NSString *)version checkIfExists:(BOOL)checkIfExists {
969 if ([node class] == [PithosSubdirNode class]) {
970 // XXX newFilename and version are ignored in the case of a subdir node for now
971 // Operation: Download a subdir node and its descendants
972 // The resulting ASIPithosObjectRequests are chained through dependencies
973 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
974 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
975 if (operation.isCancelled) {
979 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithPithos:pithos
980 containerName:node.pithosContainer.name
981 objectName:node.pithosObject.name
983 checkIfExists:checkIfExists
984 sharingAccount:node.sharingAccount];
985 if (!operation.isCancelled && objectRequests) {
986 ASIPithosObjectRequest *previousObjectRequest = nil;
987 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
988 if (operation.isCancelled) {
992 objectRequest.delegate = self;
993 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
994 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
995 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
996 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
997 message:[messagePrefix stringByAppendingString:@" (0%)"]
998 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1000 dispatch_async(dispatch_get_main_queue(), ^{
1001 [activityFacility updateActivity:activity withMessage:activity.message];
1003 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1004 [NSDictionary dictionaryWithObjectsAndKeys:
1005 activity, @"activity",
1006 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1007 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1008 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1009 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1010 [NSNumber numberWithUnsignedInteger:10], @"retries",
1011 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1012 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1013 downloadNetworkQueue, @"networkQueue",
1014 @"download", @"operationType",
1016 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1017 [activityFacility updateActivity:activity
1018 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1019 totalBytes:activity.totalBytes
1020 currentBytes:(activity.currentBytes + size)];
1022 if (previousObjectRequest)
1023 [objectRequest addDependency:previousObjectRequest];
1024 previousObjectRequest = objectRequest;
1025 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1030 [downloadQueue addOperation:operation];
1031 } else if ([node class] == [PithosObjectNode class]) {
1032 // Operation: Download an object node
1033 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1034 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1035 if (operation.isCancelled) {
1039 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithPithos:pithos
1040 containerName:node.pithosContainer.name
1041 objectName:node.pithosObject.name
1044 withNewFileName:newFileName
1045 checkIfExists:checkIfExists
1046 sharingAccount:node.sharingAccount];
1047 if (!operation.isCancelled && objectRequest) {
1048 objectRequest.delegate = self;
1049 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1050 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1051 NSString *messagePrefix = [NSString stringWithFormat:@"Downloading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1052 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
1053 message:[messagePrefix stringByAppendingString:@" (0%)"]
1054 totalBytes:node.pithosObject.bytes
1056 dispatch_async(dispatch_get_main_queue(), ^{
1057 [activityFacility updateActivity:activity withMessage:activity.message];
1059 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1060 [NSDictionary dictionaryWithObjectsAndKeys:
1061 activity, @"activity",
1062 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1063 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1064 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1065 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1066 [NSNumber numberWithUnsignedInteger:10], @"retries",
1067 NSStringFromSelector(@selector(downloadObjectFinished:)), @"didFinishSelector",
1068 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1069 downloadNetworkQueue, @"networkQueue",
1070 @"download", @"operationType",
1072 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
1073 [activityFacility updateActivity:activity
1074 withMessage:[messagePrefix stringByAppendingFormat:@" (%.0f%%)", (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1075 totalBytes:activity.totalBytes
1076 currentBytes:(activity.currentBytes + size)];
1078 [downloadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1082 [downloadQueue addOperation:operation];
1086 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
1087 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
1089 NSFileManager *fileManager = [NSFileManager defaultManager];
1090 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1091 NSString *objectNamePrefix;
1092 if ([destinationNode class] == [PithosSubdirNode class])
1093 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1095 objectNamePrefix = [NSString string];
1096 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
1097 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithPithos:pithos
1098 containerName:containerName];
1099 ASINetworkQueue *networkQueue = [ASINetworkQueue queue];
1101 [networkQueue addOperations:[NSArray arrayWithObject:[PithosUtilities prepareRequest:containerRequest]] waitUntilFinished:YES];
1102 if ([containerRequest error]) {
1103 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1105 } else if (containerRequest.responseStatusCode != 204) {
1106 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1109 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
1110 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
1112 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
1113 NSString *blockHash = destinationNode.pithosContainer.blockHash;
1115 for (NSString *filePath in filenames) {
1117 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
1120 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1121 precomposedStringWithCanonicalMapping];
1122 // Operation: Upload a local file
1123 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1124 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1125 if (operation.isCancelled) {
1129 NSError *error = nil;
1130 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
1131 if (contentType == nil)
1132 contentType = @"application/octet-stream";
1134 NSLog(@"contentType detection error: %@", error);
1135 NSArray *hashes = nil;
1136 if (operation.isCancelled) {
1140 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1141 containerName:containerName
1142 objectName:objectName
1143 contentType:contentType
1149 sharingAccount:destinationNode.sharingAccount];
1150 if (!operation.isCancelled && objectRequest) {
1151 objectRequest.delegate = self;
1152 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1153 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1154 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1155 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1156 message:[messagePrefix stringByAppendingString:@" (0%)"]
1157 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1159 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1160 [NSDictionary dictionaryWithObjectsAndKeys:
1161 containerName, @"containerName",
1162 objectName, @"objectName",
1163 contentType, @"contentType",
1164 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1165 blockHash, @"blockHash",
1166 filePath, @"filePath",
1168 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
1169 [NSNumber numberWithBool:YES], @"refresh",
1170 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1171 activity, @"activity",
1172 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1173 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1174 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1175 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1176 [NSNumber numberWithUnsignedInteger:10], @"retries",
1177 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1178 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1179 uploadNetworkQueue, @"networkQueue",
1180 @"upload", @"operationType",
1182 if (destinationNode.sharingAccount)
1183 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1184 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1188 [uploadQueue addOperation:operation];
1190 // Upload directory, confirm first
1191 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1192 [alert setMessageText:@"Upload directory"];
1193 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
1194 [alert addButtonWithTitle:@"OK"];
1195 [alert addButtonWithTitle:@"Cancel"];
1196 NSInteger choice = [alert runModal];
1197 if (choice == NSAlertFirstButtonReturn) {
1198 NSString *objectName = [[objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]]
1199 precomposedStringWithCanonicalMapping];
1200 // Operation: Upload a local directory and its descendants
1201 // The resulting ASIPithosObjectRequests are chained through dependencies
1202 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1203 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1204 if (operation.isCancelled) {
1208 NSMutableArray *objectNames = nil;
1209 NSMutableArray *contentTypes = nil;
1210 NSMutableArray *filePaths = nil;
1211 NSMutableArray *hashesArrays = nil;
1212 NSMutableArray *directoryObjectRequests = nil;
1213 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithPithos:pithos
1214 containerName:containerName
1215 objectName:objectName
1218 forDirectory:filePath
1220 objectNames:&objectNames
1221 contentTypes:&contentTypes
1222 filePaths:&filePaths
1223 hashesArrays:&hashesArrays
1224 directoryObjectRequests:&directoryObjectRequests
1225 sharingAccount:destinationNode.sharingAccount];
1226 if (operation.isCancelled) {
1230 ASIPithosObjectRequest *previousObjectRequest = nil;
1231 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
1232 if (operation.isCancelled) {
1236 objectRequest.delegate = self;
1237 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1238 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1239 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1240 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1241 message:messagePrefix];
1242 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1243 [NSDictionary dictionaryWithObjectsAndKeys:
1244 [NSNumber numberWithBool:YES], @"refresh",
1245 activity, @"activity",
1246 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1247 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1248 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1249 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1250 [NSNumber numberWithUnsignedInteger:10], @"retries",
1251 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
1252 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1253 uploadNetworkQueue, @"networkQueue",
1254 @"upload", @"operationType",
1256 if (previousObjectRequest)
1257 [objectRequest addDependency:previousObjectRequest];
1258 previousObjectRequest = objectRequest;
1259 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1261 if (!operation.isCancelled && objectRequests) {
1262 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
1263 if (operation.isCancelled) {
1267 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
1268 objectRequest.delegate = self;
1269 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1270 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1271 NSString *messagePrefix = [NSString stringWithFormat:@"Uploading '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
1272 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
1273 message:[messagePrefix stringByAppendingString:@" (0%)"]
1274 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
1276 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
1277 [NSDictionary dictionaryWithObjectsAndKeys:
1278 containerName, @"containerName",
1279 [objectNames objectAtIndex:i], @"objectName",
1280 [contentTypes objectAtIndex:i], @"contentType",
1281 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
1282 blockHash, @"blockHash",
1283 [filePaths objectAtIndex:i], @"filePath",
1284 [hashesArrays objectAtIndex:i], @"hashes",
1285 [NSNumber numberWithBool:YES], @"refresh",
1286 [NSNumber numberWithUnsignedInteger:10], @"iteration",
1287 activity, @"activity",
1288 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1289 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1290 [messagePrefix stringByAppendingString:@" (100%)"], @"finishedActivityMessage",
1291 [NSNumber numberWithInteger:NSOperationQueuePriorityNormal], @"priority",
1292 [NSNumber numberWithUnsignedInteger:10], @"retries",
1293 NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)), @"didFinishSelector",
1294 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1295 uploadNetworkQueue, @"networkQueue",
1296 @"upload", @"operationType",
1298 if (destinationNode.sharingAccount)
1299 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
1300 if (previousObjectRequest)
1301 [objectRequest addDependency:previousObjectRequest];
1302 previousObjectRequest = objectRequest;
1303 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest]];
1308 [uploadQueue addOperation:operation];
1316 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1317 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1318 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1320 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1321 NSString *objectNamePrefix;
1322 if ([destinationNode class] == [PithosSubdirNode class])
1323 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1325 objectNamePrefix = [NSString string];
1327 for (PithosNode *node in nodes) {
1328 if (([node class] == [PithosObjectNode class]) ||
1329 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1330 // Operation: Move an object or subdir/ node
1331 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1332 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1333 if (operation.isCancelled) {
1337 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1338 if ([node.pithosObject.name hasSuffix:@"/"])
1339 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1340 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
1341 containerName:node.pithosContainer.name
1342 objectName:node.pithosObject.name
1343 destinationContainerName:containerName
1344 destinationObjectName:destinationObjectName
1346 if (!operation.isCancelled && objectRequest) {
1347 objectRequest.delegate = self;
1348 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1349 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1350 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1351 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1352 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1353 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1354 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1355 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1356 message:messagePrefix];
1357 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1358 [NSDictionary dictionaryWithObjectsAndKeys:
1359 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1360 activity, @"activity",
1361 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1362 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1363 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1364 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1365 [NSNumber numberWithUnsignedInteger:10], @"retries",
1366 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1367 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1368 moveNetworkQueue, @"networkQueue",
1369 @"move", @"operationType",
1371 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1375 [moveQueue addOperation:operation];
1376 } else if ([node class] == [PithosSubdirNode class]) {
1377 // Operation: Move a subdir node and its descendants
1378 // The resulting ASIPithosObjectRequests are chained through dependencies
1379 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1380 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1381 if (operation.isCancelled) {
1385 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1386 if (node.pithosObject.subdir)
1387 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1388 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
1389 containerName:node.pithosContainer.name
1390 objectName:node.pithosObject.name
1391 destinationContainerName:containerName
1392 destinationObjectName:destinationObjectName
1394 if (!operation.isCancelled && objectRequests) {
1395 ASIPithosObjectRequest *previousObjectRequest = nil;
1396 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1397 if (operation.isCancelled) {
1401 objectRequest.delegate = self;
1402 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1403 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1404 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1405 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1406 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1407 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1408 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1409 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1410 message:messagePrefix];
1411 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1412 [NSDictionary dictionaryWithObjectsAndKeys:
1413 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
1414 [NSNumber numberWithBool:YES], @"refresh",
1415 activity, @"activity",
1416 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1417 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1418 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1419 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1420 [NSNumber numberWithUnsignedInteger:10], @"retries",
1421 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
1422 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1423 moveNetworkQueue, @"networkQueue",
1424 @"move", @"operationType",
1426 if (previousObjectRequest)
1427 [objectRequest addDependency:previousObjectRequest];
1428 previousObjectRequest = objectRequest;
1429 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1434 [moveQueue addOperation:operation];
1440 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
1441 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
1442 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
1444 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
1445 NSString *objectNamePrefix;
1446 if ([destinationNode class] == [PithosSubdirNode class])
1447 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
1449 objectNamePrefix = [NSString string];
1451 for (PithosNode *node in nodes) {
1452 if (([node class] == [PithosObjectNode class]) ||
1453 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1454 // Operation: Copy an object or subdir/ node
1455 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1456 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1457 if (operation.isCancelled) {
1461 NSString *destinationObjectName;
1462 if (![destinationNode isEqualTo:node.parent]) {
1463 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1464 if ([node.pithosObject.name hasSuffix:@"/"])
1465 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1467 destinationObjectName = [PithosUtilities safeObjectNameForPithos:pithos
1468 containerName:containerName
1469 objectName:node.pithosObject.name];
1471 if (operation.isCancelled) {
1475 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithPithos:pithos
1476 containerName:node.pithosContainer.name
1477 objectName:node.pithosObject.name
1478 destinationContainerName:containerName
1479 destinationObjectName:destinationObjectName
1481 sharingAccount:node.sharingAccount];
1482 if (!operation.isCancelled && objectRequest) {
1483 objectRequest.delegate = self;
1484 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1485 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1486 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1487 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1488 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1489 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1490 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1491 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1492 message:messagePrefix];
1493 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1494 [NSDictionary dictionaryWithObjectsAndKeys:
1495 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1496 activity, @"activity",
1497 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1498 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1499 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1500 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1501 [NSNumber numberWithUnsignedInteger:10], @"retries",
1502 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1503 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1504 copyNetworkQueue, @"networkQueue",
1505 @"copy", @"operationType",
1507 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1511 [copyQueue addOperation:operation];
1512 } else if ([node class] == [PithosSubdirNode class]) {
1513 // Operation: Copy a subdir node and its descendants
1514 // The resulting ASIPithosObjectRequests are chained through dependencies
1515 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
1516 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1517 if (operation.isCancelled) {
1521 NSString *destinationObjectName;
1522 if (![destinationNode isEqualTo:node.parent]) {
1523 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1524 if (node.pithosObject.subdir)
1525 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1527 destinationObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
1528 containerName:containerName
1529 subdirName:node.pithosObject.name];
1531 if (operation.isCancelled) {
1535 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithPithos:pithos
1536 containerName:node.pithosContainer.name
1537 objectName:node.pithosObject.name
1538 destinationContainerName:containerName
1539 destinationObjectName:destinationObjectName
1541 sharingAccount:node.sharingAccount];
1542 if (!operation.isCancelled && objectRequests) {
1543 ASIPithosObjectRequest *previousObjectRequest = nil;
1544 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1545 if (operation.isCancelled) {
1549 objectRequest.delegate = self;
1550 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1551 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1552 NSString *messagePrefix = [NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1553 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1554 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1555 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1556 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
1557 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1558 message:messagePrefix];
1559 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1560 [NSDictionary dictionaryWithObjectsAndKeys:
1561 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1562 activity, @"activity",
1563 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
1564 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
1565 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
1566 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
1567 [NSNumber numberWithUnsignedInteger:10], @"retries",
1568 NSStringFromSelector(@selector(copyFinished:)), @"didFinishSelector",
1569 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
1570 copyNetworkQueue, @"networkQueue",
1571 @"copy", @"operationType",
1573 if (previousObjectRequest)
1574 [objectRequest addDependency:previousObjectRequest];
1575 previousObjectRequest = objectRequest;
1576 [copyNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
1581 [copyQueue addOperation:operation];
1588 #pragma mark ASIHTTPRequestDelegate
1590 - (void)performRequestFinishedDelegateInBackground:(ASIPithosRequest *)request {
1591 NSOperationQueue *callbackQueue;
1592 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1593 if ([operationType isEqualToString:@"move"])
1594 callbackQueue = moveCallbackQueue;
1595 else if ([operationType isEqualToString:@"copy"])
1596 callbackQueue = copyCallbackQueue;
1597 else if ([operationType isEqualToString:@"delete"])
1598 callbackQueue = deleteCallbackQueue;
1599 else if ([operationType isEqualToString:@"upload"])
1600 callbackQueue = uploadCallbackQueue;
1601 else if ([operationType isEqualToString:@"download"])
1602 callbackQueue = downloadCallbackQueue;
1604 dispatch_async(dispatch_get_main_queue(), ^{
1605 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1606 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1610 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1611 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1612 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFinishSelector"])
1613 object:request] autorelease];
1614 operation.completionBlock = ^{
1615 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1616 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1617 dispatch_async(dispatch_get_main_queue(), ^{
1618 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1619 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1624 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1625 [callbackQueue addOperation:operation];
1628 - (void)performRequestFailedDelegateInBackground:(ASIPithosRequest *)request {
1629 if (request.isCancelled) {
1630 // Request has been cancelled
1631 // The callbackQueue might be suspended so we call directly the callback method, since it does minimal work anyway
1632 [self performSelector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1633 withObject:request];
1635 NSOperationQueue *callbackQueue;
1636 NSString *operationType = [request.userInfo objectForKey:@"operationType"];
1637 if ([operationType isEqualToString:@"move"])
1638 callbackQueue = moveCallbackQueue;
1639 else if ([operationType isEqualToString:@"copy"])
1640 callbackQueue = copyCallbackQueue;
1641 else if ([operationType isEqualToString:@"delete"])
1642 callbackQueue = deleteCallbackQueue;
1643 else if ([operationType isEqualToString:@"upload"])
1644 callbackQueue = uploadCallbackQueue;
1645 else if ([operationType isEqualToString:@"download"])
1646 callbackQueue = downloadCallbackQueue;
1648 dispatch_async(dispatch_get_main_queue(), ^{
1649 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1650 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1654 // Add an operation to the callbackQueue with a completionBlock for the case of cancellation
1655 NSInvocationOperation *operation = [[[NSInvocationOperation alloc] initWithTarget:self
1656 selector:NSSelectorFromString([request.userInfo objectForKey:@"didFailSelector"])
1657 object:request] autorelease];
1658 operation.completionBlock = ^{
1659 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1660 if ([[request.userInfo objectForKey:@"operation"] isCancelled]) {
1661 dispatch_async(dispatch_get_main_queue(), ^{
1662 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1663 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1668 [(NSMutableDictionary *)request.userInfo setObject:operation forKey:@"operation"];
1669 [callbackQueue addOperation:operation];
1673 - (void)requestFailed:(ASIPithosRequest *)request {
1674 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1675 NSOperation *operation = [request.userInfo objectForKey:@"operation"];
1676 NSLog(@"Request failed: %@", request.url);
1677 if (operation.isCancelled) {
1681 if (request.isCancelled) {
1682 dispatch_async(dispatch_get_main_queue(), ^{
1683 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1684 withMessage:[request.userInfo objectForKey:@"stoppedActivityMessage"]];
1689 NSUInteger retries = [[request.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1691 ASIPithosRequest *newRequest = (ASIPithosRequest *)[[PithosUtilities copyRequest:request] autorelease];
1692 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1693 [(NSMutableDictionary *)(newRequest.userInfo)setObject:[NSNumber numberWithBool:NO] forKey:@"unexpectedResponseStatus"];
1694 [[newRequest.userInfo objectForKey:@"networkQueue"] addOperation:[PithosUtilities prepareRequest:newRequest priority:[[newRequest.userInfo objectForKey:@"priority"] integerValue]]];
1696 dispatch_async(dispatch_get_main_queue(), ^{
1697 [activityFacility endActivity:[request.userInfo objectForKey:@"activity"]
1698 withMessage:[request.userInfo objectForKey:@"failedActivityMessage"]];
1700 if ([[request.userInfo objectForKey:@"unexpectedResponseStatus"] boolValue])
1701 [PithosUtilities unexpectedResponseStatusAlertWithRequest:request];
1703 [PithosUtilities httpRequestErrorAlertWithRequest:request];
1708 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1709 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1710 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1711 NSLog(@"Download finished: %@", objectRequest.url);
1712 if (operation.isCancelled) {
1713 [self requestFailed:objectRequest];
1714 } else if (objectRequest.responseStatusCode == 200) {
1715 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1716 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1717 NSUInteger totalBytes = activity.totalBytes;
1719 // XXX change contentLength to objectContentLength if it is fixed in the server
1720 if ([objectRequest contentLength] == 0) {
1721 // The check above was:
1722 // if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1723 // I checked for directory content types in order not to create a file in place of a directory,
1724 // but this callback method is not called in the case of a directory download.
1725 // It maybe the case though, when downloading an old version of an object, is of a directory content type.
1726 // In this case, a file should be created. This is actually a feature that allows you to hide data in a directory object.
1727 NSLog(@"Downloaded 0 bytes");
1728 NSFileManager *fileManager = [NSFileManager defaultManager];
1729 if (![fileManager fileExistsAtPath:filePath]) {
1730 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1731 dispatch_async(dispatch_get_main_queue(), ^{
1732 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1733 [alert setMessageText:@"Create File Error"];
1734 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1735 [alert addButtonWithTitle:@"OK"];
1742 NSUInteger currentBytes = [objectRequest objectContentLength];
1743 if (currentBytes == 0)
1744 currentBytes = totalBytes;
1745 dispatch_async(dispatch_get_main_queue(), ^{
1746 [activityFacility endActivity:activity
1747 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1748 totalBytes:totalBytes
1749 currentBytes:currentBytes];
1752 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1753 [self requestFailed:objectRequest];
1758 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1759 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1760 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1761 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1762 if (operation.isCancelled) {
1763 [self requestFailed:objectRequest];
1764 } else if (objectRequest.responseStatusCode == 201) {
1765 NSLog(@"Directory object created: %@", objectRequest.url);
1766 dispatch_async(dispatch_get_main_queue(), ^{
1767 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1768 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1769 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1770 [node forceRefresh];
1772 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1775 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1776 [self forceRefresh:self];
1777 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1778 [self refresh:self];
1781 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1782 [self requestFailed:objectRequest];
1787 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1788 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1789 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1790 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1791 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1792 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1793 NSUInteger totalBytes = activity.totalBytes;
1794 NSUInteger currentBytes = activity.currentBytes;
1795 if (operation.isCancelled) {
1796 [self requestFailed:objectRequest];
1797 } else if (objectRequest.responseStatusCode == 201) {
1798 NSLog(@"Object created: %@", objectRequest.url);
1799 dispatch_async(dispatch_get_main_queue(), ^{
1800 [activityFacility endActivity:activity
1801 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]
1802 totalBytes:totalBytes
1803 currentBytes:totalBytes];
1804 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1805 [node forceRefresh];
1807 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1810 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1811 [self forceRefresh:self];
1812 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1813 [self refresh:self];
1815 } else if (objectRequest.responseStatusCode == 409) {
1816 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1817 if (iteration == 0) {
1818 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1819 dispatch_async(dispatch_get_main_queue(), ^{
1820 [activityFacility endActivity:activity
1821 withMessage:[objectRequest.userInfo objectForKey:@"failedActivityMessage"]];
1822 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1823 [alert setMessageText:@"Upload Timeout"];
1824 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1825 [objectRequest.userInfo objectForKey:@"objectName"]]];
1826 [alert addButtonWithTitle:@"OK"];
1832 NSLog(@"object is missing hashes: %@", objectRequest.url);
1833 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1834 withMissingHashes:[objectRequest hashes]];
1835 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1836 if (totalBytes >= [missingBlocks count]*blockSize)
1837 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1838 dispatch_async(dispatch_get_main_queue(), ^{
1839 [activityFacility updateActivity:activity
1840 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (totalBytes ? (100*(currentBytes + 0.0)/(totalBytes + 0.0)) : 100)]
1841 totalBytes:totalBytes
1842 currentBytes:currentBytes];
1844 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1845 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1846 containerName:[objectRequest.userInfo objectForKey:@"containerName"]
1848 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1849 missingBlockIndex:missingBlockIndex
1850 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1851 newContainerRequest.delegate = self;
1852 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1853 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1854 newContainerRequest.userInfo = objectRequest.userInfo;
1855 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1856 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1857 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1858 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1859 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadMissingBlockFinished:)) forKey:@"didFinishSelector"];
1860 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1861 [activityFacility updateActivity:activity
1862 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1863 totalBytes:activity.totalBytes
1864 currentBytes:(activity.currentBytes + size)];
1866 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1868 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1869 [self requestFailed:objectRequest];
1874 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1875 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1876 NSOperation *operation = [containerRequest.userInfo objectForKey:@"operation"];
1877 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1878 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1879 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1880 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1881 if (operation.isCancelled) {
1882 [self requestFailed:containerRequest];
1883 } else if (containerRequest.responseStatusCode == 202) {
1884 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1885 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1886 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1887 if (missingBlockIndex == NSNotFound) {
1888 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1889 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithPithos:pithos
1890 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1891 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1892 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1894 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1895 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1898 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1899 newObjectRequest.delegate = self;
1900 newObjectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1901 newObjectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1902 newObjectRequest.userInfo = containerRequest.userInfo;
1903 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1904 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1905 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1906 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:NSStringFromSelector(@selector(uploadObjectUsingHashMapFinished:)) forKey:@"didFinishSelector"];
1907 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newObjectRequest priority:[[newObjectRequest.userInfo objectForKey:@"priority"] integerValue]]];
1909 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithPithos:pithos
1910 containerName:[containerRequest.userInfo objectForKey:@"containerName"]
1911 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1912 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1913 missingBlockIndex:missingBlockIndex
1914 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1915 newContainerRequest.delegate = self;
1916 newContainerRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
1917 newContainerRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
1918 newContainerRequest.userInfo = containerRequest.userInfo;
1919 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1920 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1921 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1922 [activityFacility updateActivity:activity
1923 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (activity.totalBytes ? (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0)) : 100)]
1924 totalBytes:activity.totalBytes
1925 currentBytes:(activity.currentBytes + size)];
1927 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:newContainerRequest priority:[[newContainerRequest.userInfo objectForKey:@"priority"] integerValue]]];
1930 [(NSMutableDictionary *)(containerRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1931 [self requestFailed:containerRequest];
1936 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1937 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1938 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1939 NSLog(@"Move object finished: %@", objectRequest.url);
1940 if (operation.isCancelled) {
1941 [self requestFailed:objectRequest];
1942 } else if (objectRequest.responseStatusCode == 201) {
1943 dispatch_async(dispatch_get_main_queue(), ^{
1944 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1945 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1946 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1947 [node forceRefresh];
1949 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1952 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1953 [self forceRefresh:self];
1954 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1955 [self refresh:self];
1958 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1959 [self requestFailed:objectRequest];
1964 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1965 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1966 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1967 NSLog(@"Copy object finished: %@", objectRequest.url);
1968 if (operation.isCancelled) {
1969 [self requestFailed:objectRequest];
1970 } else if (objectRequest.responseStatusCode == 201) {
1971 dispatch_async(dispatch_get_main_queue(), ^{
1972 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1973 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
1974 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1975 [node forceRefresh];
1977 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1980 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1981 [self forceRefresh:self];
1982 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1983 [self refresh:self];
1986 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
1987 [self requestFailed:objectRequest];
1992 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1993 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
1994 NSOperation *operation = [objectRequest.userInfo objectForKey:@"operation"];
1995 NSLog(@"Delete object finished: %@", objectRequest.url);
1996 if (operation.isCancelled) {
1997 [self requestFailed:objectRequest];
1998 } else if (objectRequest.responseStatusCode == 204) {
1999 dispatch_async(dispatch_get_main_queue(), ^{
2000 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
2001 withMessage:[objectRequest.userInfo objectForKey:@"finishedActivityMessage"]];
2002 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
2003 [node forceRefresh];
2005 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
2008 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
2009 [self forceRefresh:self];
2010 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
2011 [self refresh:self];
2014 [(NSMutableDictionary *)(objectRequest.userInfo)setObject:[NSNumber numberWithBool:YES] forKey:@"unexpectedResponseStatus"];
2015 [self requestFailed:objectRequest];
2021 #pragma mark NSSplitViewDelegate
2023 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
2024 if (splitView == verticalSplitView)
2027 return ([horizontalSplitView bounds].size.height - 108);
2030 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
2031 if (splitView == verticalSplitView)
2034 return ([horizontalSplitView bounds].size.height - 108);
2037 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
2038 if (splitView == verticalSplitView) {
2039 if (proposedPosition < 120)
2041 else if (proposedPosition > 220)
2044 return proposedPosition;
2046 return ([horizontalSplitView bounds].size.height - 108);
2051 #pragma mark NSOutlineViewDataSource
2053 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
2054 if (!browserInitialized)
2058 if (item == containersNode)
2059 return containersNodeChildren.count;
2060 if (item == sharedNode)
2065 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
2066 if (!browserInitialized)
2069 return (!index ? containersNode : sharedNode);
2070 if (item == sharedNode)
2071 return (!index ? mySharedNode : othersSharedNode);
2072 return [containersNodeChildren objectAtIndex:index];
2075 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
2076 if ((item == containersNode) || (item == sharedNode))
2081 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
2082 PithosNode *node = (PithosNode *)item;
2086 #pragma mark Drag and Drop destination
2088 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
2089 validateDrop:(id<NSDraggingInfo>)info
2090 proposedItem:(id)item
2091 proposedChildIndex:(NSInteger)index {
2092 NSDragOperation result = NSDragOperationNone;
2093 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
2095 PithosNode *dropNode = (PithosNode *)item;
2096 if ([dropNode class] != [PithosContainerNode class])
2098 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2099 result = NSDragOperationCopy;
2100 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2101 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2102 ([info draggingSourceOperationMask] & NSDragOperationMove)) {
2103 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
2104 if (![dropNode isEqualTo:draggedParentNode])
2105 result = NSDragOperationMove;
2106 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
2107 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
2108 result = NSDragOperationCopy;
2114 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
2115 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
2116 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
2117 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
2118 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
2119 PithosNode *node = (PithosNode *)item;
2120 NSLog(@"drag in node: %@", node.url);
2121 return [self uploadFiles:filenames toNode:node];
2123 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
2124 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
2125 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
2126 PithosNode *node = (PithosNode *)item;
2127 NSLog(@"drag local node: %@", node.url);
2128 if (![[draggedNodes objectAtIndex:0] shared] && ![[draggedNodes objectAtIndex:0] sharingAccount] &&
2129 ([info draggingSourceOperationMask] & NSDragOperationMove))
2130 return [self moveNodes:draggedNodes toNode:node];
2131 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
2132 return [self copyNodes:draggedNodes toNode:node];
2139 #pragma mark NSOutlineViewDelegate
2141 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
2142 if ((item == containersNode) || (item == sharedNode))
2147 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
2148 if ((item == containersNode) || (item == sharedNode))
2153 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
2154 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
2157 [browser loadColumnZero];
2163 #pragma mark NSMenuDelegate
2165 - (void)menuNeedsUpdate:(NSMenu *)menu {
2166 [menu removeAllItems];
2167 NSMenuItem *menuItem;
2168 NSString *menuItemTitle;
2169 BOOL nodeContextMenu = NO;
2170 PithosNode *menuNode = nil;
2171 NSMutableArray *menuNodes;
2172 if (menu == browserMenu) {
2173 NSInteger column = [browser clickedColumn];
2174 NSInteger row = [browser clickedRow];
2175 if ((column == -1) || (row == -1)) {
2177 // General context menu
2178 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2179 if ([menuNodesIndexPaths count] == 0) {
2180 menuNode = [browser parentForItemsInColumn:0];
2181 } else if (([menuNodesIndexPaths count] != 1) ||
2182 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
2183 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2185 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2188 menuNode = [browser parentForItemsInColumn:column];
2189 if ([menuNode class] == [PithosObjectNode class]) {
2190 // Node context menu
2191 menuNodes = [NSMutableArray arrayWithObject:menuNode];
2192 nodeContextMenu = YES;
2195 // General context menu
2198 // Node context menu
2199 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
2200 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2201 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2202 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
2203 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2204 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2207 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
2209 nodeContextMenu = YES;
2211 } else if (menu == outlineViewMenu) {
2212 NSInteger row = [outlineView clickedRow];
2214 row = [outlineView selectedRow];
2217 menuNode = [outlineView itemAtRow:row];
2220 if (!nodeContextMenu) {
2221 // General context menu
2222 if (([menuNode class] == [PithosAccountNode class]) ||
2223 ([menuNode class] == [PithosSharingAccountsNode class]) ||
2224 ([menuNode class] == [PithosEmptyNode class]))
2227 if (!menuNode.shared && !menuNode.sharingAccount) {
2228 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
2229 [menuItem setRepresentedObject:menuNode];
2230 [menu addItem:menuItem];
2231 [menu addItem:[NSMenuItem separatorItem]];
2234 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2235 [menu addItem:menuItem];
2236 [menu addItem:[NSMenuItem separatorItem]];
2238 menuItem = [[[NSMenuItem alloc] initWithTitle:(([menuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2239 action:@selector(menuGetInfo:)
2240 keyEquivalent:@""] autorelease];
2241 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
2242 [menu addItem:menuItem];
2244 if (clipboardNodes && !menuNode.shared && !menuNode.sharingAccount &&
2245 (([menuNode class] == [PithosContainerNode class]) ||
2246 (([menuNode class] == [PithosSubdirNode class]) &&
2247 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])))) {
2248 NSUInteger clipboardNodesCount = [clipboardNodes count];
2249 if (clipboardNodesCount == 0) {
2250 self.clipboardNodes = nil;
2251 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
2252 if (clipboardNodesCount == 1)
2253 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2255 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2256 [menu addItem:[NSMenuItem separatorItem]];
2257 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2258 [menuItem setRepresentedObject:menuNode];
2259 [menu addItem:menuItem];
2263 // Node context menu
2264 NSUInteger menuNodesCount = [menuNodes count];
2265 PithosNode *firstMenuNode = [menuNodes objectAtIndex:0];
2267 if (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class])) {
2268 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Download" action:@selector(menuDownload:) keyEquivalent:@""] autorelease];
2269 [menuItem setRepresentedObject:menuNodes];
2270 [menu addItem:menuItem];
2271 [menu addItem:[NSMenuItem separatorItem]];
2273 // Move to Trash (pithos container only)
2275 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount && ([rootNode class] == [PithosContainerNode class])) {
2276 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
2277 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash"
2278 action:@selector(menuMoveToTrash:)
2279 keyEquivalent:@""] autorelease];
2280 [menuItem setRepresentedObject:menuNodes];
2281 [menu addItem:menuItem];
2283 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
2284 [menuItem setRepresentedObject:menuNodes];
2285 [menu addItem:menuItem];
2286 [menu addItem:[NSMenuItem separatorItem]];
2289 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Refresh" action:@selector(refresh:) keyEquivalent:@""] autorelease];
2290 [menu addItem:menuItem];
2292 if (!firstMenuNode.sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
2293 [menu addItem:[NSMenuItem separatorItem]];
2294 menuItem = [[[NSMenuItem alloc] initWithTitle:(([firstMenuNode class] == [PithosContainerNode class]) ? @"Info" : @"Info and Sharing")
2295 action:@selector(menuGetInfo:)
2296 keyEquivalent:@""] autorelease];
2297 [menuItem setRepresentedObject:menuNodes];
2298 [menu addItem:menuItem];
2300 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
2301 [menu addItem:[NSMenuItem separatorItem]];
2304 if (!firstMenuNode.shared && !firstMenuNode.sharingAccount) {
2305 if (menuNodesCount == 1)
2306 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2308 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
2309 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
2310 [menuItem setRepresentedObject:menuNodes];
2311 [menu addItem:menuItem];
2314 if ((!firstMenuNode.shared && !firstMenuNode.sharingAccount) ||
2315 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
2316 if (menuNodesCount == 1)
2317 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
2319 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
2320 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
2321 [menuItem setRepresentedObject:menuNodes];
2322 [menu addItem:menuItem];
2325 if (clipboardNodes && !firstMenuNode.shared && !firstMenuNode.sharingAccount && (menuNodesCount == 1) &&
2326 ([firstMenuNode class] == [PithosSubdirNode class]) &&
2327 (firstMenuNode.pithosObject.subdir || ![firstMenuNode.pithosObject.name hasSuffix:@"/"])) {
2328 NSUInteger clipboardNodesCount = [clipboardNodes count];
2329 if (clipboardNodesCount == 0) {
2330 self.clipboardNodes = nil;
2331 } else if (clipboardCopy || ![firstMenuNode isEqualTo:clipboardParentNode]) {
2332 if (clipboardNodesCount == 1)
2333 menuItemTitle = [NSString stringWithString:@"Paste Item"];
2335 menuItemTitle = [NSString stringWithString:@"Paste Items"];
2336 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
2337 [menuItem setRepresentedObject:firstMenuNode];
2338 [menu addItem:menuItem];
2345 #pragma mark NSMenuValidation
2347 - (BOOL)validateMenuItem:(NSMenuItem *)menuItem {
2348 if ((menuItem.action == @selector(cut:)) || (menuItem.action == @selector(copy:)) || (menuItem.action == @selector(delete:))) {
2349 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2350 if ([menuNodesIndexPaths count] == 0)
2353 PithosNode *firstMenuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2354 if (((menuItem.action == @selector(cut:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount)) ||
2355 ((menuItem.action == @selector(copy:)) && (firstMenuNode.shared || firstMenuNode.sharingAccount) &&
2356 (([firstMenuNode class] == [PithosContainerNode class]) || ([firstMenuNode class] == [PithosAccountNode class]))) ||
2357 ((menuItem.action == @selector(delete:)) &&
2358 (firstMenuNode.shared || firstMenuNode.sharingAccount || ([rootNode class] != [PithosContainerNode class]) ||
2359 ((menuItem.tag == 0) && ![rootNode.pithosContainer.name isEqualToString:@"pithos"]))))
2362 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
2363 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
2364 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
2366 menuItem.representedObject = menuNodes;
2367 } else if (menuItem.action == @selector(paste:)) {
2368 if (!clipboardNodes || ![clipboardNodes count])
2371 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
2372 PithosNode *menuNode;
2373 if ([menuNodesIndexPaths count] == 0)
2374 menuNode = [browser parentForItemsInColumn:0];
2375 else if (([menuNodesIndexPaths count] != 1) ||
2376 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class]))
2377 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
2379 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
2381 if (menuNode.shared || menuNode.sharingAccount ||
2382 (([menuNode class] != [PithosContainerNode class]) &&
2383 (([menuNode class] != [PithosSubdirNode class]) ||
2384 (!menuNode.pithosObject.subdir && [menuNode.pithosObject.name hasSuffix:@"/"]))) ||
2385 (!clipboardCopy && [menuNode isEqualTo:clipboardParentNode]))
2388 menuItem.representedObject = menuNode;
2393 - (void)cut:(NSMenuItem *)sender {
2394 [self menuCut:sender];
2397 - (void)copy:(NSMenuItem *)sender {
2398 [self menuCopy:sender];
2401 - (void)paste:(NSMenuItem *)sender {
2402 [self menuPaste:sender];
2405 - (void)delete:(NSMenuItem *)sender {
2406 if (sender.tag == 0)
2407 [self menuMoveToTrash:sender];
2409 [self menuDelete:sender];
2413 #pragma mark Menu Actions
2415 - (void)menuNewFolder:(NSMenuItem *)sender {
2416 PithosNode *node = (PithosNode *)[sender representedObject];
2417 if ([node class] == [PithosContainerNode class]) {
2418 // Operation: Create (upload) a new root application/directory object
2419 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2420 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2421 if (operation.isCancelled) {
2425 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2426 containerName:node.pithosContainer.name
2427 subdirName:@"untitled folder"];
2428 NSString *fileName = [safeObjectName lastPathComponent];
2429 if (operation.isCancelled) {
2433 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2434 containerName:node.pithosContainer.name
2435 objectName:safeObjectName
2437 contentType:@"application/directory"
2439 contentDisposition:nil
2442 isPublic:ASIPithosObjectRequestPublicIgnore
2444 data:[NSData data]];
2445 objectRequest.delegate = self;
2446 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2447 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2448 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2449 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2450 message:messagePrefix];
2451 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2452 fileName, @"fileName",
2453 [NSArray arrayWithObject:node], @"refreshNodes",
2454 [NSNumber numberWithBool:YES], @"refresh",
2455 activity, @"activity",
2456 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2457 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2458 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2459 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2460 [NSNumber numberWithUnsignedInteger:10], @"retries",
2461 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2462 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2463 uploadNetworkQueue, @"networkQueue",
2464 @"upload", @"operationType",
2466 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2469 [uploadQueue addOperation:operation];
2470 } else if (([node class] == [PithosSubdirNode class]) &&
2471 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
2472 // Operation: Create (upload) a new aplication/directory object
2473 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2474 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2475 if (operation.isCancelled) {
2479 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2480 containerName:node.pithosContainer.name
2481 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
2482 NSString *fileName = [safeObjectName lastPathComponent];
2483 if (operation.isCancelled) {
2487 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos
2488 containerName:node.pithosContainer.name
2489 objectName:safeObjectName
2491 contentType:@"application/directory"
2493 contentDisposition:nil
2496 isPublic:ASIPithosObjectRequestPublicIgnore
2498 data:[NSData data]];
2499 objectRequest.delegate = self;
2500 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2501 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2502 NSString *messagePrefix = [NSString stringWithFormat:@"Creating directory '%@'", fileName];
2503 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
2504 message:messagePrefix];
2505 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2506 fileName, @"fileName",
2507 [NSArray arrayWithObject:node], @"refreshNodes",
2508 [NSNumber numberWithBool:YES], @"refresh",
2509 activity, @"activity",
2510 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2511 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2512 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2513 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2514 [NSNumber numberWithUnsignedInteger:10], @"retries",
2515 NSStringFromSelector(@selector(uploadDirectoryObjectFinished:)), @"didFinishSelector",
2516 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2517 uploadNetworkQueue, @"networkQueue",
2518 @"upload", @"operationType",
2520 [uploadNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2523 [uploadQueue addOperation:operation];
2527 - (void)menuGetInfo:(NSMenuItem *)sender {
2528 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2529 [node showPithosNodeInfo:sender];
2533 - (void)menuDownload:(NSMenuItem *)sender {
2534 NSArray *nodes = (NSArray *)[sender representedObject];
2535 PithosNode *firstNode = [nodes objectAtIndex:0];
2536 if (([nodes count] == 1) && ([firstNode class] == [PithosObjectNode class])) {
2537 NSSavePanel *save = [NSSavePanel savePanel];
2538 save.nameFieldStringValue = firstNode.displayName;
2539 int result = [save runModal];
2540 if (result == NSOKButton) {
2541 NSString *destinationPath = save.URL.path;
2542 NSString *directoryPath = [destinationPath stringByDeletingLastPathComponent];
2543 NSString *newFileName = [destinationPath lastPathComponent];
2544 if ([destinationPath hasSuffix:@"/"])
2545 newFileName = [newFileName stringByAppendingString:@"/"];
2546 if ([firstNode.displayName isEqualToString:newFileName])
2548 [self downloadNode:firstNode toDirectory:directoryPath withNewFileName:newFileName version:nil checkIfExists:NO];
2551 NSOpenPanel *open = [NSOpenPanel openPanel];
2552 open.canChooseFiles = NO;
2553 open.canChooseDirectories = YES;
2554 open.canCreateDirectories = YES;
2555 int result = [open runModal];
2556 if (result == NSOKButton) {
2557 NSString *directoryPath = open.URL.path;
2558 for (PithosNode *node in nodes) {
2559 [self downloadNode:node toDirectory:directoryPath withNewFileName:nil version:nil checkIfExists:YES];
2565 - (void)menuDelete:(NSMenuItem *)sender {
2566 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2567 if (([node class] == [PithosObjectNode class]) ||
2568 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
2569 // Operation: Delete an object or subdir/ node
2570 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2571 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2572 if (operation.isCancelled) {
2576 NSString *fileName = [node.pithosObject.name lastPathComponent];
2577 if ([node.pithosObject.name hasSuffix:@"/"])
2578 fileName = [fileName stringByAppendingString:@"/"];
2579 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos
2580 containerName:node.pithosContainer.name
2581 objectName:node.pithosObject.name];
2582 if (operation.isCancelled) {
2586 objectRequest.delegate = self;
2587 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2588 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2589 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", fileName];
2590 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2591 message:messagePrefix];
2592 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
2593 fileName, @"fileName",
2594 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2595 activity, @"activity",
2596 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2597 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2598 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2599 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2600 [NSNumber numberWithUnsignedInteger:10], @"retries",
2601 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2602 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2603 deleteNetworkQueue, @"networkQueue",
2604 @"delete", @"operationType",
2606 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2609 [deleteQueue addOperation:operation];
2610 } else if ([node class] == [PithosSubdirNode class]) {
2611 // Operation: Delete a subdir node and its descendants
2612 // The resulting ASIPithosObjectRequests are chained through dependencies
2613 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2614 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2615 if (operation.isCancelled) {
2619 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithPithos:pithos
2620 containerName:node.pithosContainer.name
2621 objectName:node.pithosObject.name];
2622 if (!operation.isCancelled && objectRequests) {
2623 ASIPithosObjectRequest *previousObjectRequest = nil;
2624 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2625 if (operation.isCancelled) {
2629 objectRequest.delegate = self;
2630 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2631 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2632 NSString *messagePrefix = [NSString stringWithFormat:@"Deleting '%@'", [objectRequest.userInfo objectForKey:@"fileName"]];
2633 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
2634 message:messagePrefix];
2635 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2636 [NSDictionary dictionaryWithObjectsAndKeys:
2637 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2638 activity, @"activity",
2639 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2640 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2641 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2642 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2643 [NSNumber numberWithUnsignedInteger:10], @"retries",
2644 NSStringFromSelector(@selector(deleteObjectFinished:)), @"didFinishSelector",
2645 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2646 deleteNetworkQueue, @"networkQueue",
2647 @"delete", @"operationType",
2649 if (previousObjectRequest)
2650 [objectRequest addDependency:previousObjectRequest];
2651 previousObjectRequest = objectRequest;
2652 [deleteNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2657 [deleteQueue addOperation:operation];
2662 - (void)menuMoveToTrash:(NSMenuItem *)sender {
2663 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
2664 if (([node class] == [PithosObjectNode class]) ||
2665 (([node class] == [PithosSubdirNode class]) &&
2666 !node.pithosObject.subdir &&
2667 [node.pithosObject.name hasSuffix:@"/"])) {
2668 // Operation: Move to trash an object or subdir/ node
2669 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2670 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2671 if (operation.isCancelled) {
2675 NSString *safeObjectName = [PithosUtilities safeObjectNameForPithos:pithos
2676 containerName:@"trash"
2677 objectName:node.pithosObject.name];
2678 if (!operation.isCancelled && safeObjectName) {
2679 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithPithos:pithos
2680 containerName:node.pithosContainer.name
2681 objectName:node.pithosObject.name
2682 destinationContainerName:@"trash"
2683 destinationObjectName:safeObjectName
2685 if (!operation.isCancelled && objectRequest) {
2686 objectRequest.delegate = self;
2687 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2688 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2689 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2690 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2691 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2692 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2693 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2694 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2695 message:messagePrefix];
2696 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2697 [NSDictionary dictionaryWithObjectsAndKeys:
2698 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2699 activity, @"activity",
2700 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2701 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2702 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2703 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2704 [NSNumber numberWithUnsignedInteger:10], @"retries",
2705 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2706 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2707 moveNetworkQueue, @"networkQueue",
2708 @"move", @"operationType",
2710 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2715 [moveQueue addOperation:operation];
2716 } else if ([node class] == [PithosSubdirNode class]) {
2717 // Operation: Move to trash a subdir node and its descendants
2718 // The resulting ASIPithosObjectRequests are chained through dependencies
2719 __block NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
2720 NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
2721 if (operation.isCancelled) {
2725 NSString *safeObjectName = [PithosUtilities safeSubdirNameForPithos:pithos
2726 containerName:@"trash"
2727 subdirName:node.pithosObject.name];
2728 if (!operation.isCancelled && safeObjectName) {
2729 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithPithos:pithos
2730 containerName:node.pithosContainer.name
2731 objectName:node.pithosObject.name
2732 destinationContainerName:@"trash"
2733 destinationObjectName:safeObjectName
2735 if (!operation.isCancelled && objectRequests) {
2736 ASIPithosObjectRequest *previousObjectRequest = nil;
2737 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2738 if (operation.isCancelled) {
2742 objectRequest.delegate = self;
2743 objectRequest.didFinishSelector = @selector(performRequestFinishedDelegateInBackground:);
2744 objectRequest.didFailSelector = @selector(performRequestFailedDelegateInBackground:);
2745 NSString *messagePrefix = [NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2746 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2747 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2748 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2749 [objectRequest.userInfo objectForKey:@"destinationObjectName"]];
2750 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2751 message:messagePrefix];
2752 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2753 [NSDictionary dictionaryWithObjectsAndKeys:
2754 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2755 activity, @"activity",
2756 [messagePrefix stringByAppendingString:@" (stopped)"], @"stoppedActivityMessage",
2757 [messagePrefix stringByAppendingString:@" (failed)"], @"failedActivityMessage",
2758 [messagePrefix stringByAppendingString:@" (finished)"], @"finishedActivityMessage",
2759 [NSNumber numberWithInteger:NSOperationQueuePriorityHigh], @"priority",
2760 [NSNumber numberWithUnsignedInteger:10], @"retries",
2761 NSStringFromSelector(@selector(moveFinished:)), @"didFinishSelector",
2762 NSStringFromSelector(@selector(requestFailed:)), @"didFailSelector",
2763 moveNetworkQueue, @"networkQueue",
2764 @"move", @"operationType",
2766 if (previousObjectRequest)
2767 [objectRequest addDependency:previousObjectRequest];
2768 previousObjectRequest = objectRequest;
2769 [moveNetworkQueue addOperation:[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityHigh]];
2775 [moveQueue addOperation:operation];
2780 - (void)menuCut:(NSMenuItem *)sender {
2781 self.clipboardNodes = (NSArray *)[sender representedObject];
2782 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2783 self.clipboardCopy = NO;
2786 - (void)menuCopy:(NSMenuItem *)sender {
2787 self.clipboardNodes = (NSArray *)[sender representedObject];
2788 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2789 self.clipboardCopy = YES;
2792 - (void)menuPaste:(NSMenuItem *)sender {
2793 if (!clipboardNodes || ![clipboardNodes count])
2795 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2796 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2797 if (clipboardCopy) {
2798 [self copyNodes:localClipboardNodes toNode:dropNode];
2799 } else if (![dropNode isEqualTo:clipboardParentNode]) {
2800 self.clipboardNodes = nil;
2801 self.clipboardParentNode = nil;
2802 [self moveNodes:localClipboardNodes toNode:dropNode];
2807 #pragma mark PithosActivityFacilityDelegate
2809 - (void)activityUpdate:(NSDictionary *)info {
2810 NSString *message = [info objectForKey:@"message"];
2811 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2812 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2813 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2814 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2815 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2816 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2817 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2818 if (runningActivitiesCount && totalBytes) {
2819 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2820 [activityProgressIndicator startAnimation:self];
2822 [activityProgressIndicator setDoubleValue:1.0];
2823 [activityProgressIndicator stopAnimation:self];
2827 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2828 [activityTextField setStringValue:message];