2 // PithosBrowserController.m
5 // Copyright 2011 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 "ASIPithosRequest.h"
49 #import "ASIPithosContainerRequest.h"
50 #import "ASIPithosObjectRequest.h"
51 #import "ASIPithosAccount.h"
52 #import "ASIPithosContainer.h"
53 #import "ASIPithosObject.h"
54 #import "PithosUtilities.h"
55 #import "UsingSizeTransformer.h"
57 @interface PithosBrowserCell : FileSystemBrowserCell {}
60 @implementation PithosBrowserCell
63 if ((self = [super init])) {
64 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
65 [self setEditable:YES];
70 - (void)setObjectValue:(id)object {
71 if ([object isKindOfClass:[PithosNode class]]) {
72 PithosNode *node = (PithosNode *)object;
73 [self setStringValue:node.displayName];
74 [self setImage:node.icon];
76 [super setObjectValue:object];
82 @interface PithosOutlineViewCell : ImageAndTextCell {}
85 @implementation PithosOutlineViewCell
87 - (void)setObjectValue:(id)object {
88 if ([object isKindOfClass:[PithosNode class]]) {
89 PithosNode *node = (PithosNode *)object;
90 [self setStringValue:node.displayName];
91 [self setImage:node.icon];
92 [self setEditable:NO];
94 [super setObjectValue:object];
100 @interface PithosBrowserController (Private)
101 - (void)resetContainers:(NSNotification *)notification;
102 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode;
103 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
104 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode;
107 @implementation PithosBrowserController
108 @synthesize accountNode;
109 @synthesize verticalSplitView, horizontalSplitView, leftTopView, leftBottomView, outlineView, browser, outlineViewMenu, browserMenu;
110 @synthesize draggedNodes, draggedParentNode;
111 @synthesize clipboardNodes, clipboardParentNode, clipboardCopy;
112 @synthesize activityTextField, activityProgressIndicator;
115 #pragma Object Lifecycle
118 return [super initWithWindowNibName:@"PithosBrowserController"];
122 [[NSNotificationCenter defaultCenter] removeObserver:self];
123 [refreshTimer invalidate];
124 [refreshTimer release];
125 [clipboardParentNode release];
126 [clipboardNodes release];
127 [draggedParentNode release];
128 [draggedNodes release];
129 [sharedPreviewController release];
130 [othersSharedNode release];
131 [mySharedNode release];
132 [sharedNode release];
133 [containersNodeChildren release];
134 [containersNode release];
135 [accountNode release];
140 - (void)awakeFromNib {
141 [super awakeFromNib];
143 [browser registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
144 [browser setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
145 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
147 [outlineView registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, NSFilesPromisePboardType, nil]];
148 [outlineView setDraggingSourceOperationMask:(NSDragOperationCopy|NSDragOperationMove) forLocal:YES];
149 [outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
151 [browser setCellClass:[PithosBrowserCell class]];
154 - (void)resetContainers:(NSNotification *)notification {
155 [refreshTimer invalidate];
156 [refreshTimer release];
159 [browser loadColumnZero];
160 [containersNodeChildren removeAllObjects];
161 [outlineView reloadData];
162 // Expand the folder outline view
163 [outlineView expandItem:nil expandChildren:YES];
164 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
167 [accountNode refresh];
168 [mySharedNode refresh];
169 [othersSharedNode refresh];
171 [activityFacility reset];
173 refreshTimer = [[NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(forceRefresh:) userInfo:self repeats:YES] retain];
176 - (void)windowDidLoad {
177 [super windowDidLoad];
179 [activityProgressIndicator setUsesThreadedAnimation:YES];
180 [activityProgressIndicator setMinValue:0.0];
181 [activityProgressIndicator setMaxValue:1.0];
182 activityFacility = [PithosActivityFacility defaultPithosActivityFacility];
183 activityFacility.delegate = self;
185 self.accountNode = [[[PithosAccountNode alloc] init] autorelease];
186 containersNode = [[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil];
187 containersNodeChildren = [[NSMutableArray alloc] init];
188 sharedNode = [[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil];
189 mySharedNode = [[PithosAccountNode alloc] init];
190 mySharedNode.displayName = @"my shared";
191 mySharedNode.shared = YES;
192 mySharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)];
193 othersSharedNode = [[PithosSharingAccountsNode alloc] init];
194 othersSharedNode.displayName = @"others shared";
195 othersSharedNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)];
197 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
199 // Register for updates
200 // PithosContainerNode updates browser nodes
201 [[NSNotificationCenter defaultCenter] addObserver:self
202 selector:@selector(pithosNodeChildrenUpdated:)
203 name:@"PithosContainerNodeChildrenUpdated"
205 // PithosSubdirNode updates browser nodes
206 [[NSNotificationCenter defaultCenter] addObserver:self
207 selector:@selector(pithosNodeChildrenUpdated:)
208 name:@"PithosSubdirNodeChildrenUpdated"
210 // PithosAccountNode accountNode updates outlineView container nodes
211 [[NSNotificationCenter defaultCenter] addObserver:self
212 selector:@selector(pithosAccountNodeChildrenUpdated:)
213 name:@"PithosAccountNodeChildrenUpdated"
215 // PithosAccountNode other than accountNode updates nodes
216 [[NSNotificationCenter defaultCenter] addObserver:self
217 selector:@selector(pithosNodeChildrenUpdated:)
218 name:@"PithosAccountNodeChildrenUpdated"
220 // PithosSharingAccountsNode othersSharedNode updates browser nodes
221 [[NSNotificationCenter defaultCenter] addObserver:self
222 selector:@selector(pithosNodeChildrenUpdated:)
223 name:@"PithosSharingAccountsNodeChildrenUpdated"
224 object:othersSharedNode];
225 // Updated authentication credentials reset containers in the outline view
226 [[NSNotificationCenter defaultCenter] addObserver:self
227 selector:@selector(resetContainers:)
228 name:@"PithosAuthenticationCredentialsUpdated"
230 // Request for browser refresh
231 [[NSNotificationCenter defaultCenter] addObserver:self
232 selector:@selector(pithosBrowserRefreshNeeded:)
233 name:@"PithosBrowserRefreshNeeeded"
238 #pragma mark Observers
240 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
241 PithosNode *node = (PithosNode *)[notification object];
242 if (node == accountNode)
244 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
245 NSInteger lastColumn = [browser lastColumn];
246 for (NSInteger column = lastColumn; column >= 0; column--) {
247 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
248 [browser reloadColumn:column];
254 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
255 BOOL containerPithosFound = NO;
256 BOOL containerTrashFound = NO;
257 NSMutableIndexSet *removedContainersNodeChildren = [NSMutableIndexSet indexSet];
258 for (NSUInteger i = 0 ; i < [containersNodeChildren count] ; i++) {
259 if (![accountNode.children containsObject:[containersNodeChildren objectAtIndex:i]])
260 [removedContainersNodeChildren addIndex:i];
262 [containersNodeChildren removeObjectsAtIndexes:removedContainersNodeChildren];
263 for (PithosContainerNode *containerNode in accountNode.children) {
264 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
265 if (![containersNodeChildren containsObject:containerNode])
266 [containersNodeChildren insertObject:containerNode atIndex:0];
267 containerPithosFound = YES;
268 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
269 NSUInteger insertIndex = 1;
270 if (!containerPithosFound)
272 if (![containersNodeChildren containsObject:containerNode])
273 [containersNodeChildren insertObject:containerNode atIndex:insertIndex];
274 containerTrashFound = YES;
275 } else if (![containersNodeChildren containsObject:containerNode]) {
276 [containersNodeChildren addObject:containerNode];
279 BOOL refreshAccountNode = NO;
280 if (!containerPithosFound) {
281 // Create pithos node
282 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
283 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
284 while (![containerRequest isFinished]) {
287 if ([containerRequest error]) {
288 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
290 refreshAccountNode = YES;
293 if (!containerTrashFound) {
295 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
296 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
297 while (![containerRequest isFinished]) {
300 if ([containerRequest error]) {
301 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
303 refreshAccountNode = YES;
307 if (refreshAccountNode)
308 [accountNode refresh];
310 [outlineView reloadData];
312 // Expand the folder outline view
313 [outlineView expandItem:nil expandChildren:YES];
315 if ((rootNode == nil) || (rootNode == containersNode) || (rootNode == sharedNode)) {
316 rootNode = [containersNodeChildren objectAtIndex:0];
317 [browser loadColumnZero];
324 - (void)pithosBrowserRefreshNeeded:(NSNotification *)notification {
331 - (IBAction)forceRefresh:(id)sender {
333 [accountNode forceRefresh];
334 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
335 PithosNode *node = (PithosNode *)[browser parentForItemsInColumn:column];
336 node.forcedRefresh = YES;
337 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
339 [browser validateVisibleColumns];
342 - (IBAction)refresh:(id)sender {
343 if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
344 [self forceRefresh:sender];
347 [accountNode refresh];
348 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
349 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
351 [browser validateVisibleColumns];
356 #pragma mark NSBrowserDelegate
358 - (id)rootItemForBrowser:(NSBrowser *)browser {
362 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
363 PithosNode *node = (PithosNode *)item;
364 return node.children.count;
367 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
368 PithosNode *node = (PithosNode *)item;
369 return [node.children objectAtIndex:index];
372 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
373 PithosNode *node = (PithosNode *)item;
374 return node.isLeafItem;
377 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
378 PithosNode *node = (PithosNode *)item;
382 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
383 if (sharedPreviewController == nil)
384 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
385 return sharedPreviewController;
388 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
389 // if (!forUserResize) {
390 // id item = [browser parentForItemsInColumn:columnIndex];
391 // if ([self browser:browser isLeafItem:item]) {
392 // suggestedWidth = 200;
395 // return suggestedWidth;
398 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
404 - (BOOL)browser:(NSBrowser *)browser shouldEditItem:(id)item {
405 PithosNode *node = (PithosNode *)item;
406 if (node.shared || node.sharingAccount ||
407 ([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class]))
412 - (void)browser:(NSBrowser *)browser setObjectValue:(id)object forItem:(id)item {
413 PithosNode *node = (PithosNode *)item;
414 NSString *newName = (NSString *)object;
415 NSUInteger newNameLength = [newName length];
416 NSRange firstSlashRange = [newName rangeOfString:@"/"];
417 if ((newNameLength == 0) ||
418 ((firstSlashRange.length == 1) && (firstSlashRange.location != (newNameLength - 1))) ||
419 ([newName isEqualToString:node.displayName])) {
422 if (([node class] == [PithosObjectNode class]) ||
423 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
424 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
425 dispatch_async(queue, ^{
426 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
427 if ([newName hasSuffix:@"/"])
428 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
429 NSError *error = nil;
431 if ([PithosUtilities objectExistsAtContainerName:node.pithosContainer.name
432 objectName:destinationObjectName
434 isDirectory:&isDirectory
435 sharingAccount:nil]) {
436 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
437 [alert setMessageText:@"Name Taken"];
438 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
439 [alert addButtonWithTitle:@"OK"];
445 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
446 objectName:node.pithosObject.name
447 destinationContainerName:node.pithosContainer.name
448 destinationObjectName:destinationObjectName
451 objectRequest.delegate = self;
452 objectRequest.didFinishSelector = @selector(moveFinished:);
453 objectRequest.didFailSelector = @selector(moveFailed:);
454 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
455 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
456 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
457 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
458 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
459 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
460 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
461 [NSDictionary dictionaryWithObjectsAndKeys:
462 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
463 [NSNumber numberWithBool:YES], @"refresh",
464 activity, @"activity",
465 [NSNumber numberWithUnsignedInteger:10], @"retries",
467 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
470 } else if ([node class] == [PithosSubdirNode class]) {
471 if (firstSlashRange.length == 1)
473 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
474 dispatch_async(queue, ^{
475 NSString *destinationObjectName = [[node.pithosObject.name stringByDeletingLastPathComponent] stringByAppendingPathComponent:newName];
476 NSError *error = nil;
478 if ([PithosUtilities objectExistsAtContainerName:node.pithosContainer.name
479 objectName:destinationObjectName
481 isDirectory:&isDirectory
482 sharingAccount:nil]) {
483 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
484 [alert setMessageText:@"Name Taken"];
485 [alert setInformativeText:[NSString stringWithFormat:@"The name '%@' is already taken. Please choose a different name", newName]];
486 [alert addButtonWithTitle:@"OK"];
492 if (node.pithosObject.subdir)
493 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
494 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
495 objectName:node.pithosObject.name
496 destinationContainerName:node.pithosContainer.name
497 destinationObjectName:destinationObjectName
499 if (objectRequests) {
500 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
501 objectRequest.delegate = self;
502 objectRequest.didFinishSelector = @selector(moveFinished:);
503 objectRequest.didFailSelector = @selector(moveFailed:);
504 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
505 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
506 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
507 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
508 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
509 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
510 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
511 [NSDictionary dictionaryWithObjectsAndKeys:
512 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
513 [NSNumber numberWithBool:YES], @"refresh",
514 activity, @"activity",
515 [NSNumber numberWithUnsignedInteger:10], @"retries",
517 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
524 #pragma mark Drag and Drop source
526 - (BOOL)browser:(NSBrowser *)aBrowser canDragRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
527 withEvent:(NSEvent *)event {
528 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
529 __block BOOL result = YES;
530 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
531 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
532 if (([node class] == [PithosContainerNode class]) || ([node class] == [PithosAccountNode class])) {
540 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
541 toPasteboard:(NSPasteboard *)pasteboard {
542 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
543 NSMutableArray *nodes = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
544 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
545 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
546 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
547 [propertyList addObject:[node.pithosObject.name pathExtension]];
548 [nodes addObject:node];
551 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
552 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
553 self.draggedNodes = nodes;
554 self.draggedParentNode = [browser parentForItemsInColumn:column];
558 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
559 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
560 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[draggedNodes count]];
561 for (PithosNode *node in draggedNodes) {
562 // If the node is a subdir ask if the whole tree should be downloaded
563 if ([node class] == [PithosSubdirNode class]) {
564 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
565 [alert setMessageText:@"Download directory"];
566 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
567 [alert addButtonWithTitle:@"OK"];
568 [alert addButtonWithTitle:@"Cancel"];
569 NSInteger choice = [alert runModal];
570 if (choice == NSAlertFirstButtonReturn) {
571 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
572 dispatch_async(queue, ^{
573 NSArray *objectRequests = [PithosUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
574 objectName:node.pithosObject.name
575 toDirectory:[dropDestination path]
577 sharingAccount:node.sharingAccount];
578 if (objectRequests) {
579 for (__block ASIPithosObjectRequest *objectRequest in objectRequests) {
580 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
581 objectRequest.delegate = self;
582 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
583 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
584 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
585 message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo objectForKey:@"fileName"]]
586 totalBytes:[[objectRequest.userInfo objectForKey:@"bytes"] unsignedIntegerValue]
588 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
589 [NSDictionary dictionaryWithObjectsAndKeys:
590 activity, @"activity",
591 [NSNumber numberWithUnsignedInteger:10], @"retries",
593 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
594 [activityFacility updateActivity:activity
595 withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", [objectRequest.userInfo valueForKey:@"fileName"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
596 totalBytes:activity.totalBytes
597 currentBytes:(activity.currentBytes + size)];
599 [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
605 __block ASIPithosObjectRequest *objectRequest = [PithosUtilities objectDataRequestWithContainerName:node.pithosContainer.name
606 objectName:node.pithosObject.name
607 toDirectory:[dropDestination path]
609 sharingAccount:node.sharingAccount];
611 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
612 objectRequest.delegate = self;
613 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
614 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
615 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDownload
616 message:[NSString stringWithFormat:@"Downloading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
617 totalBytes:node.pithosObject.bytes
619 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
620 [NSDictionary dictionaryWithObjectsAndKeys:
621 activity, @"activity",
622 [NSNumber numberWithUnsignedInteger:10], @"retries",
624 [objectRequest setBytesReceivedBlock:^(unsigned long long size, unsigned long long total){
625 [activityFacility updateActivity:activity
626 withMessage:[NSString stringWithFormat:@"Downloading '%@' (%.0f%%)", [objectRequest.userInfo valueForKey:@"fileName"], (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
627 totalBytes:activity.totalBytes
628 currentBytes:(activity.currentBytes + size)];
630 [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
637 #pragma mark Drag and Drop destination
639 - (NSDragOperation)browser:aBrowser
640 validateDrop:(id<NSDraggingInfo>)info
641 proposedRow:(NSInteger *)row
642 column:(NSInteger *)column
643 dropOperation:(NSBrowserDropOperation *)dropOperation {
644 NSDragOperation result = NSDragOperationNone;
645 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
646 // For a drop above, the drop is redirected to the parent item
647 if (*dropOperation == NSBrowserDropAbove)
649 // Only allow dropping in folders
651 PithosNode *dropNode;
653 // Check if the node is not a folder and if so redirect to the parent item
654 dropNode = [browser itemAtRow:*row inColumn:*column];
655 if ([dropNode class] == [PithosObjectNode class])
659 dropNode = [browser parentForItemsInColumn:*column];
661 if (!dropNode.shared &&
662 (!dropNode.sharingAccount ||
663 ([dropNode class] == [PithosSubdirNode class]) ||
664 ([dropNode class] == [PithosContainerNode class]))) {
665 *dropOperation = NSBrowserDropOn;
666 result = NSDragOperationCopy;
669 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
670 // For a drop above, the drop is redirected to the parent item
671 if (*dropOperation == NSBrowserDropAbove)
673 // Only allow dropping in folders
675 PithosNode *dropNode;
677 // Check if the node is not a folder and if so redirect to the parent item
678 dropNode = [browser itemAtRow:*row inColumn:*column];
679 if ([dropNode class] == [PithosObjectNode class])
683 dropNode = [browser parentForItemsInColumn:*column];
685 if (!dropNode.shared && !dropNode.sharingAccount) {
686 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
687 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
688 if ((([dropNode class] == [PithosContainerNode class]) ||
689 dropNode.pithosObject.subdir ||
690 ![dropNode.pithosObject.name hasSuffix:@"/"]) &&
691 ![dropNode isEqualTo:draggedParentNode]) {
692 // ![dropNode isEqualTo:draggedParentNode] &&
693 // ![draggedNodes containsObject:dropNode]) {
694 result = NSDragOperationMove;
696 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
697 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
698 if (([dropNode class] == [PithosContainerNode class]) ||
699 dropNode.pithosObject.subdir ||
700 ![dropNode.pithosObject.name hasSuffix:@"/"]) {
701 result = NSDragOperationCopy;
710 - (BOOL)browser:(NSBrowser *)aBrowser
711 acceptDrop:(id<NSDraggingInfo>)info
713 column:(NSInteger)column
714 dropOperation:(NSBrowserDropOperation)dropOperation {
715 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
716 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
717 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
718 if ((column != -1) && (filenames != nil)) {
721 node = [browser itemAtRow:row inColumn:column];
723 node = [browser parentForItemsInColumn:column];
724 NSLog(@"drag in node: %@", node.url);
725 return [self uploadFiles:filenames toNode:node];
727 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
728 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
729 if ((column != -1) && (draggedNodes != nil)) {
732 node = [browser itemAtRow:row inColumn:column];
734 node = [browser parentForItemsInColumn:column];
735 NSLog(@"drag local node: %@", node.url);
736 if ([info draggingSourceOperationMask] & NSDragOperationMove)
737 return [self moveNodes:draggedNodes toNode:node];
738 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
739 return [self copyNodes:draggedNodes toNode:node];
746 #pragma mark Drag and Drop methods
748 - (BOOL)uploadFiles:(NSArray *)filenames toNode:(PithosNode *)destinationNode {
749 if (([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class]))
751 NSFileManager *fileManager = [NSFileManager defaultManager];
752 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
753 NSString *objectNamePrefix;
754 if ([destinationNode class] == [PithosSubdirNode class])
755 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
757 objectNamePrefix = [NSString string];
758 if ((destinationNode.pithosContainer.blockHash == nil) || (destinationNode.pithosContainer.blockSize == 0)) {
759 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest containerMetadataRequestWithContainerName:containerName];
760 [[PithosUtilities prepareRequest:containerRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
761 while (![containerRequest isFinished]) {
764 if ([containerRequest error]) {
765 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
767 } else if (containerRequest.responseStatusCode != 204) {
768 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
771 destinationNode.pithosContainer.blockHash = [containerRequest blockHash];
772 destinationNode.pithosContainer.blockSize = [containerRequest blockSize];
774 NSUInteger blockSize = destinationNode.pithosContainer.blockSize;
775 NSString *blockHash = destinationNode.pithosContainer.blockHash;
777 for (NSString *filePath in filenames) {
779 if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
782 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
783 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
784 dispatch_async(queue, ^{
785 NSError *error = nil;
786 NSString *contentType = [PithosUtilities contentTypeOfFile:filePath error:&error];
787 if (contentType == nil)
788 contentType = @"application/octet-stream";
790 NSLog(@"contentType detection error: %@", error);
791 NSArray *hashes = nil;
792 ASIPithosObjectRequest *objectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:containerName
793 objectName:objectName
794 contentType:contentType
800 sharingAccount:destinationNode.sharingAccount];
802 objectRequest.delegate = self;
803 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
804 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
805 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
806 message:[NSString stringWithFormat:@"Uploading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
807 totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
809 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
810 [NSDictionary dictionaryWithObjectsAndKeys:
811 containerName, @"containerName",
812 objectName, @"objectName",
813 contentType, @"contentType",
814 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
815 blockHash, @"blockHash",
816 filePath, @"filePath",
818 [NSArray arrayWithObject:destinationNode], @"refreshNodes",
819 [NSNumber numberWithBool:YES], @"refresh",
820 [NSNumber numberWithUnsignedInteger:10], @"iteration",
821 activity, @"activity",
822 [NSNumber numberWithUnsignedInteger:10], @"retries",
824 if (destinationNode.sharingAccount)
825 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
826 [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
830 // Upload directory, confirm first
831 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
832 [alert setMessageText:@"Upload directory"];
833 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
834 [alert addButtonWithTitle:@"OK"];
835 [alert addButtonWithTitle:@"Cancel"];
836 NSInteger choice = [alert runModal];
837 if (choice == NSAlertFirstButtonReturn) {
838 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
839 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
840 dispatch_async(queue, ^{
841 NSMutableArray *objectNames = nil;
842 NSMutableArray *contentTypes = nil;
843 NSMutableArray *filePaths = nil;
844 NSMutableArray *hashesArrays = nil;
845 NSMutableArray *directoryObjectRequests = nil;
846 NSArray *objectRequests = [PithosUtilities writeObjectDataRequestsWithContainerName:containerName
847 objectName:objectName
850 forDirectory:filePath
852 objectNames:&objectNames
853 contentTypes:&contentTypes
855 hashesArrays:&hashesArrays
856 directoryObjectRequests:&directoryObjectRequests
857 sharingAccount:destinationNode.sharingAccount];
858 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
859 objectRequest.delegate = self;
860 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
861 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
862 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
863 message:[NSString stringWithFormat:@"Creating directory '%@'", [objectRequest.userInfo valueForKey:@"fileName"]]];
864 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
865 [NSDictionary dictionaryWithObjectsAndKeys:
866 [NSNumber numberWithBool:YES], @"refresh",
867 activity, @"activity",
868 [NSNumber numberWithUnsignedInteger:10], @"retries",
870 [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
872 if (objectRequests) {
873 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
874 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
875 objectRequest.delegate = self;
876 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
877 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
878 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityUpload
879 message:[NSString stringWithFormat:@"Uploading '%@' (0%%)", [objectRequest.userInfo valueForKey:@"fileName"]]
880 totalBytes:[[objectRequest.userInfo valueForKey:@"bytes"] unsignedIntegerValue]
882 [(NSMutableDictionary *)objectRequest.userInfo addEntriesFromDictionary:
883 [NSDictionary dictionaryWithObjectsAndKeys:
884 containerName, @"containerName",
885 [objectNames objectAtIndex:i], @"objectName",
886 [contentTypes objectAtIndex:i], @"contentType",
887 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
888 blockHash, @"blockHash",
889 [filePaths objectAtIndex:i], @"filePath",
890 [hashesArrays objectAtIndex:i], @"hashes",
891 [NSNumber numberWithBool:YES], @"refresh",
892 [NSNumber numberWithUnsignedInteger:10], @"iteration",
893 activity, @"activity",
894 [NSNumber numberWithUnsignedInteger:10], @"retries",
896 if (destinationNode.sharingAccount)
897 [(NSMutableDictionary *)objectRequest.userInfo setObject:destinationNode.sharingAccount forKey:@"sharingAccount"];
898 [[PithosUtilities prepareRequest:objectRequest] startAsynchronous];
909 - (BOOL)moveNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
910 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
911 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
913 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
914 NSString *objectNamePrefix;
915 if ([destinationNode class] == [PithosSubdirNode class])
916 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
918 objectNamePrefix = [NSString string];
920 for (PithosNode *node in nodes) {
921 if (([node class] == [PithosObjectNode class]) ||
922 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
923 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
924 dispatch_async(queue, ^{
925 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
926 if ([node.pithosObject.name hasSuffix:@"/"])
927 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
928 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
929 objectName:node.pithosObject.name
930 destinationContainerName:containerName
931 destinationObjectName:destinationObjectName
934 objectRequest.delegate = self;
935 objectRequest.didFinishSelector = @selector(moveFinished:);
936 objectRequest.didFailSelector = @selector(moveFailed:);
937 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
938 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
939 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
940 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
941 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
942 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
943 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
944 [NSDictionary dictionaryWithObjectsAndKeys:
945 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
946 activity, @"activity",
947 [NSNumber numberWithUnsignedInteger:10], @"retries",
949 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
952 } else if ([node class] == [PithosSubdirNode class]) {
953 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
954 dispatch_async(queue, ^{
955 NSString *destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
956 if (node.pithosObject.subdir)
957 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
958 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
959 objectName:node.pithosObject.name
960 destinationContainerName:containerName
961 destinationObjectName:destinationObjectName
963 if (objectRequests) {
964 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
965 objectRequest.delegate = self;
966 objectRequest.didFinishSelector = @selector(moveFinished:);
967 objectRequest.didFailSelector = @selector(moveFailed:);
968 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
969 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
970 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
971 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
972 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
973 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
974 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
975 [NSDictionary dictionaryWithObjectsAndKeys:
976 [NSArray arrayWithObjects:node.parent, destinationNode, nil], @"forceRefreshNodes",
977 [NSNumber numberWithBool:YES], @"refresh",
978 activity, @"activity",
979 [NSNumber numberWithUnsignedInteger:10], @"retries",
981 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
990 - (BOOL)copyNodes:(NSArray *)nodes toNode:(PithosNode *)destinationNode {
991 if ((([destinationNode class] != [PithosSubdirNode class]) && ([destinationNode class] != [PithosContainerNode class])) ||
992 (([destinationNode class] == [PithosSubdirNode class]) && !destinationNode.pithosObject.subdir && [destinationNode.pithosObject.name hasSuffix:@"/"]))
994 NSString *containerName = [NSString stringWithString:destinationNode.pithosContainer.name];
995 NSString *objectNamePrefix;
996 if ([destinationNode class] == [PithosSubdirNode class])
997 objectNamePrefix = [NSString stringWithString:destinationNode.pithosObject.name];
999 objectNamePrefix = [NSString string];
1001 for (PithosNode *node in nodes) {
1002 if (([node class] == [PithosObjectNode class]) ||
1003 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1004 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1005 dispatch_async(queue, ^{
1006 NSString *destinationObjectName;
1007 if (![destinationNode isEqualTo:node.parent]) {
1008 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1009 if ([node.pithosObject.name hasSuffix:@"/"])
1010 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1012 destinationObjectName = [PithosUtilities safeObjectNameForContainerName:containerName
1013 objectName:node.pithosObject.name];
1015 ASIPithosObjectRequest *objectRequest = [PithosUtilities copyObjectRequestWithContainerName:node.pithosContainer.name
1016 objectName:node.pithosObject.name
1017 destinationContainerName:containerName
1018 destinationObjectName:destinationObjectName
1020 sharingAccount:node.sharingAccount];
1021 if (objectRequest) {
1022 objectRequest.delegate = self;
1023 objectRequest.didFinishSelector = @selector(copyFinished:);
1024 objectRequest.didFailSelector = @selector(copyFailed:);
1025 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1026 message:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1027 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1028 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1029 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1030 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1031 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1032 [NSDictionary dictionaryWithObjectsAndKeys:
1033 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1034 activity, @"activity",
1035 [NSNumber numberWithUnsignedInteger:10], @"retries",
1037 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1040 } else if ([node class] == [PithosSubdirNode class]) {
1041 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1042 dispatch_async(queue, ^{
1043 NSString *destinationObjectName;
1044 if (![destinationNode isEqualTo:node.parent]) {
1045 destinationObjectName = [objectNamePrefix stringByAppendingPathComponent:node.displayName];
1046 if (node.pithosObject.subdir)
1047 destinationObjectName = [destinationObjectName stringByAppendingString:@"/"];
1049 destinationObjectName = [PithosUtilities safeSubdirNameForContainerName:containerName
1050 subdirName:node.pithosObject.name];
1052 NSArray *objectRequests = [PithosUtilities copyObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1053 objectName:node.pithosObject.name
1054 destinationContainerName:containerName
1055 destinationObjectName:destinationObjectName
1057 sharingAccount:node.sharingAccount];
1058 if (objectRequests) {
1059 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1060 objectRequest.delegate = self;
1061 objectRequest.didFinishSelector = @selector(copyFinished:);
1062 objectRequest.didFailSelector = @selector(copyFailed:);
1063 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCopy
1064 message:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@'",
1065 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1066 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1067 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1068 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1069 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1070 [NSDictionary dictionaryWithObjectsAndKeys:
1071 [NSArray arrayWithObject:destinationNode], @"forceRefreshNodes",
1072 activity, @"activity",
1073 [NSNumber numberWithUnsignedInteger:10], @"retries",
1075 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1085 #pragma mark ASIHTTPRequestDelegate
1087 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1088 NSLog(@"Download finished: %@", objectRequest.url);
1089 if (objectRequest.responseStatusCode == 200) {
1090 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
1091 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1092 NSUInteger totalBytes = activity.totalBytes;
1094 // XXX change contentLength to objectContentLength if it is fixed in the server
1095 if (([objectRequest contentLength] == 0) && ![PithosUtilities isContentTypeDirectory:[objectRequest contentType]]) {
1096 NSLog(@"Downloaded 0 bytes");
1097 NSFileManager *fileManager = [NSFileManager defaultManager];
1098 if (![fileManager fileExistsAtPath:filePath]) {
1099 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1100 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1101 [alert setMessageText:@"Create File Error"];
1102 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1103 [alert addButtonWithTitle:@"OK"];
1109 NSUInteger currentBytes = [objectRequest objectContentLength];
1110 if (currentBytes == 0)
1111 currentBytes = totalBytes;
1112 [activityFacility endActivity:activity
1113 withMessage:[NSString stringWithFormat:@"Downloading '%@' (100%%)",
1114 [objectRequest.userInfo objectForKey:@"fileName"]]
1115 totalBytes:totalBytes
1116 currentBytes:currentBytes];
1118 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1120 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1121 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1122 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1124 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1125 withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)",
1126 [objectRequest.userInfo objectForKey:@"fileName"]]];
1127 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1132 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1133 NSLog(@"Download failed: %@", objectRequest.url);
1134 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1136 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1137 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1138 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1140 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1141 withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)",
1142 [objectRequest.userInfo objectForKey:@"fileName"]]];
1143 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1147 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1148 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1149 if (objectRequest.responseStatusCode == 201) {
1150 NSLog(@"Directory object created: %@", objectRequest.url);
1151 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1152 withMessage:[NSString stringWithFormat:@"Creating directory '%@' (finished)",
1153 [objectRequest.userInfo objectForKey:@"fileName"]]];
1154 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1155 [node forceRefresh];
1157 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1160 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1161 [self forceRefresh:self];
1162 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1163 [self refresh:self];
1165 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1167 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1168 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1169 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1171 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1172 withMessage:[NSString stringWithFormat:@"Creating directory '%@' (failed)",
1173 [objectRequest.userInfo objectForKey:@"fileName"]]];
1174 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1179 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1180 NSLog(@"Upload directory object failed: %@", objectRequest.url);
1181 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1183 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1184 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1185 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1187 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1188 withMessage:[NSString stringWithFormat:@"Creating directory '%@' (failed)",
1189 [objectRequest.userInfo objectForKey:@"fileName"]]];
1190 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1194 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1195 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1196 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1197 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1198 NSUInteger totalBytes = activity.totalBytes;
1199 NSUInteger currentBytes = activity.currentBytes;
1200 if (objectRequest.responseStatusCode == 201) {
1201 NSLog(@"Object created: %@", objectRequest.url);
1202 [activityFacility endActivity:activity
1203 withMessage:[NSString stringWithFormat:@"Uploading '%@' (100%%)", fileName]
1204 totalBytes:totalBytes
1205 currentBytes:totalBytes];
1206 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1207 [node forceRefresh];
1209 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1212 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1213 [self forceRefresh:self];
1214 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1215 [self refresh:self];
1216 } else if (objectRequest.responseStatusCode == 409) {
1217 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1218 if (iteration == 0) {
1219 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1220 [activityFacility endActivity:activity
1221 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1222 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1223 [alert setMessageText:@"Upload Timeout"];
1224 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1225 [objectRequest.userInfo objectForKey:@"objectName"]]];
1226 [alert addButtonWithTitle:@"OK"];
1230 NSLog(@"object is missing hashes: %@", objectRequest.url);
1231 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1232 withMissingHashesResponse:[objectRequest responseString]];
1233 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1234 if (totalBytes >= [missingBlocks count]*blockSize)
1235 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1236 [activityFacility updateActivity:activity
1237 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
1238 totalBytes:totalBytes
1239 currentBytes:currentBytes];
1240 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1241 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1243 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1244 missingBlockIndex:missingBlockIndex
1245 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1246 newContainerRequest.delegate = self;
1247 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1248 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1249 newContainerRequest.userInfo = objectRequest.userInfo;
1250 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1251 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1252 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1253 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1254 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1255 [activityFacility updateActivity:activity
1256 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1257 totalBytes:activity.totalBytes
1258 currentBytes:(activity.currentBytes + size)];
1260 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1262 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1264 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1265 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1266 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1268 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1269 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1270 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1275 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
1276 NSLog(@"Upload using hashmap failed: %@", objectRequest.url);
1277 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1279 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1280 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1281 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1283 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1284 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)",
1285 [objectRequest.userInfo objectForKey:@"fileName"]]];
1286 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1290 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1291 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1292 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1293 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1294 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1295 if (containerRequest.responseStatusCode == 202) {
1296 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1297 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1298 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1299 if (missingBlockIndex == NSNotFound) {
1300 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1301 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1302 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1303 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1305 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1306 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1309 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1310 newObjectRequest.delegate = self;
1311 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1312 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1313 newObjectRequest.userInfo = containerRequest.userInfo;
1314 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1315 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1316 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1317 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1319 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1320 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1321 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1322 missingBlockIndex:missingBlockIndex
1323 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1324 newContainerRequest.delegate = self;
1325 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1326 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1327 newContainerRequest.userInfo = containerRequest.userInfo;
1328 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1329 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1330 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1331 [activityFacility updateActivity:activity
1332 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1333 totalBytes:activity.totalBytes
1334 currentBytes:(activity.currentBytes + size)];
1336 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1339 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1341 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1342 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1343 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1345 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1346 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1347 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1352 - (void)uploadMissingBlockFailed:(ASIPithosContainerRequest *)containerRequest {
1353 NSLog(@"Upload of missing block failed: %@", containerRequest.url);
1354 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1356 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[[PithosUtilities copyRequest:containerRequest] autorelease];
1357 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1358 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1360 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1361 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)",
1362 [containerRequest.userInfo objectForKey:@"fileName"]]];
1363 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1367 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1368 NSLog(@"Move object finished: %@", objectRequest.url);
1369 if (objectRequest.responseStatusCode == 201) {
1370 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1371 withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (finished)",
1372 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1373 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1374 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1375 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1376 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1377 [node forceRefresh];
1379 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1382 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1383 [self forceRefresh:self];
1384 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1385 [self refresh:self];
1387 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1389 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1390 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1391 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1393 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1394 withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (failed)",
1395 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1396 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1397 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1398 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1399 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1404 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1405 NSLog(@"Move object failed: %@", objectRequest.url);
1406 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1408 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1409 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1410 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1412 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1413 withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (failed)",
1414 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1415 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1416 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1417 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1418 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1422 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1423 NSLog(@"Copy object finished: %@", objectRequest.url);
1424 if (objectRequest.responseStatusCode == 201) {
1425 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1426 withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (finished)",
1427 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1428 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1429 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1430 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1431 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1432 [node forceRefresh];
1434 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1437 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1438 [self forceRefresh:self];
1439 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1440 [self refresh:self];
1442 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1444 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1445 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1446 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1448 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1449 withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (failed)",
1450 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1451 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1452 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1453 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1454 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1459 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1460 NSLog(@"Copy object failed: %@", objectRequest.url);
1461 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1463 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1464 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1465 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1467 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1468 withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (failed)",
1469 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1470 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1471 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1472 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1473 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1477 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1478 NSLog(@"Delete object finished: %@", objectRequest.url);
1479 if (objectRequest.responseStatusCode == 204) {
1480 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1481 withMessage:[NSString stringWithFormat:@"Deleting '%@' (finished)",
1482 [objectRequest.userInfo objectForKey:@"fileName"]]];
1483 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1484 [node forceRefresh];
1486 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1489 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1490 [self forceRefresh:self];
1491 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1492 [self refresh:self];
1494 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1496 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1497 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1498 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1500 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1501 withMessage:[NSString stringWithFormat:@"Deleting '%@' (failed)",
1502 [objectRequest.userInfo objectForKey:@"fileName"]]];
1503 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1508 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1509 NSLog(@"Delete object failed: %@", objectRequest.url);
1510 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1512 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[[PithosUtilities copyRequest:objectRequest] autorelease];
1513 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1514 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1516 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1517 withMessage:[NSString stringWithFormat:@"Deleting '%@' (failed)",
1518 [objectRequest.userInfo objectForKey:@"fileName"]]];
1519 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1524 #pragma mark NSSplitViewDelegate
1526 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1527 if (splitView == verticalSplitView)
1530 return ([horizontalSplitView bounds].size.height - 108);
1533 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1534 if (splitView == verticalSplitView)
1537 return ([horizontalSplitView bounds].size.height - 108);
1540 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1541 if (splitView == verticalSplitView) {
1542 if (proposedPosition < 120)
1544 else if (proposedPosition > 220)
1547 return proposedPosition;
1549 return ([horizontalSplitView bounds].size.height - 108);
1554 #pragma mark NSOutlineViewDataSource
1556 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1559 if (item == containersNode)
1560 return containersNodeChildren.count;
1561 if (item == sharedNode)
1566 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1568 return (!index ? containersNode : sharedNode);
1569 if (item == sharedNode)
1570 return (!index ? mySharedNode : othersSharedNode);
1571 return [containersNodeChildren objectAtIndex:index];
1574 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1575 if ((item == containersNode) || (item == sharedNode))
1580 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1581 PithosNode *node = (PithosNode *)item;
1585 #pragma mark Drag and Drop destination
1587 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
1588 validateDrop:(id<NSDraggingInfo>)info
1589 proposedItem:(id)item
1590 proposedChildIndex:(NSInteger)index {
1591 NSDragOperation result = NSDragOperationNone;
1592 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
1594 PithosNode *dropNode = (PithosNode *)item;
1595 if ([dropNode class] != [PithosContainerNode class])
1597 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1598 result = NSDragOperationCopy;
1599 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1600 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
1601 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
1602 if (![dropNode isEqualTo:draggedParentNode])
1603 result = NSDragOperationMove;
1604 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
1605 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
1606 result = NSDragOperationCopy;
1612 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
1613 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1614 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1615 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1616 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
1617 PithosNode *node = (PithosNode *)item;
1618 NSLog(@"drag in node: %@", node.url);
1619 return [self uploadFiles:filenames toNode:node];
1621 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1622 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1623 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
1624 PithosNode *node = (PithosNode *)item;
1625 NSLog(@"drag local node: %@", node.url);
1626 if ([info draggingSourceOperationMask] & NSDragOperationMove)
1627 return [self moveNodes:draggedNodes toNode:node];
1628 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1629 return [self copyNodes:draggedNodes toNode:node];
1636 #pragma mark NSOutlineViewDelegate
1638 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1639 if ((item == containersNode) || (item == sharedNode))
1644 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1645 if ((item == containersNode) || (item == sharedNode))
1650 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1651 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1654 [browser loadColumnZero];
1660 #pragma mark NSMenuDelegate
1662 - (void)menuNeedsUpdate:(NSMenu *)menu {
1663 [menu removeAllItems];
1664 NSMenuItem *menuItem;
1665 NSString *menuItemTitle;
1666 BOOL nodeContextMenu = NO;
1667 PithosNode *menuNode = nil;
1668 NSMutableArray *menuNodes;
1669 if (menu == browserMenu) {
1670 NSInteger column = [browser clickedColumn];
1671 NSInteger row = [browser clickedRow];
1672 if ((column == -1) || (row == -1)) {
1673 // General context menu
1674 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1675 if ([menuNodesIndexPaths count] == 0) {
1676 menuNode = [browser parentForItemsInColumn:0];
1677 } else if (([menuNodesIndexPaths count] != 1) ||
1678 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1679 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1681 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1684 // Node context menu
1685 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1686 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1687 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1688 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1689 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1690 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1693 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1695 nodeContextMenu = YES;
1697 } else if (menu == outlineViewMenu) {
1698 NSInteger row = [outlineView clickedRow];
1700 row = [outlineView selectedRow];
1703 menuNode = [outlineView itemAtRow:row];
1706 if (!nodeContextMenu) {
1707 // General context menu
1708 if (([menuNode class] == [PithosAccountNode class]) ||
1709 ([menuNode class] == [PithosSharingAccountsNode class]) ||
1710 ([menuNode class] == [PithosEmptyNode class]))
1712 BOOL shared = menuNode.shared;
1713 BOOL sharingAccount = (menuNode.sharingAccount != nil);
1715 if (!shared && !sharingAccount) {
1716 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1717 [menuItem setRepresentedObject:menuNode];
1718 [menu addItem:menuItem];
1719 [menu addItem:[NSMenuItem separatorItem]];
1722 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1723 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1724 [menu addItem:menuItem];
1726 if (!shared && !sharingAccount) {
1727 if (clipboardNodes) {
1728 NSUInteger clipboardNodesCount = [clipboardNodes count];
1729 if (clipboardNodesCount == 0) {
1730 self.clipboardNodes = nil;
1731 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1732 if (clipboardNodesCount == 1)
1733 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1735 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1736 [menu addItem:[NSMenuItem separatorItem]];
1737 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1738 [menuItem setRepresentedObject:menuNode];
1739 [menu addItem:menuItem];
1744 // Node context menu
1745 NSUInteger menuNodesCount = [menuNodes count];
1746 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1747 BOOL shared = firstMenuNode.shared;
1748 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1749 // Move to Trash (pithos container only)
1751 if (!shared && !sharingAccount) {
1752 if ([rootNode class] == [PithosContainerNode class]) {
1753 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1754 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1755 [menuItem setRepresentedObject:menuNodes];
1756 [menu addItem:menuItem];
1758 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1759 [menuItem setRepresentedObject:menuNodes];
1760 [menu addItem:menuItem];
1761 [menu addItem:[NSMenuItem separatorItem]];
1765 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1766 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1767 [menuItem setRepresentedObject:menuNodes];
1768 [menu addItem:menuItem];
1770 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1771 [menu addItem:[NSMenuItem separatorItem]];
1774 if (!shared && !sharingAccount) {
1775 if (menuNodesCount == 1)
1776 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1778 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1779 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1780 [menuItem setRepresentedObject:menuNodes];
1781 [menu addItem:menuItem];
1784 if ((!shared && !sharingAccount) ||
1785 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1786 if (menuNodesCount == 1)
1787 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1789 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1790 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1791 [menuItem setRepresentedObject:menuNodes];
1792 [menu addItem:menuItem];
1795 if (!shared && !sharingAccount) {
1796 if (menuNodesCount == 1) {
1797 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1798 if (([menuNode class] == [PithosSubdirNode class]) &&
1799 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1800 if (clipboardNodes) {
1801 NSUInteger clipboardNodesCount = [clipboardNodes count];
1802 if (clipboardNodesCount == 0) {
1803 self.clipboardNodes = nil;
1804 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1805 if (clipboardNodesCount == 1)
1806 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1808 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1809 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1810 [menuItem setRepresentedObject:menuNode];
1811 [menu addItem:menuItem];
1821 #pragma mark Menu Actions
1823 - (void)menuNewFolder:(NSMenuItem *)sender {
1824 PithosNode *node = (PithosNode *)[sender representedObject];
1825 if ([node class] == [PithosContainerNode class]) {
1826 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1827 dispatch_async(queue, ^{
1828 NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1829 subdirName:@"untitled folder"];
1830 NSString *fileName = [safeObjectName lastPathComponent];
1831 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1832 objectName:safeObjectName
1834 contentType:@"application/directory"
1836 contentDisposition:nil
1839 isPublic:ASIPithosObjectRequestPublicIgnore
1841 data:[NSData data]];
1842 objectRequest.delegate = self;
1843 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
1844 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
1845 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1846 message:[NSString stringWithFormat:@"Creating directory '%@'", fileName]];
1847 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1848 fileName, @"fileName",
1849 [NSArray arrayWithObject:node], @"refreshNodes",
1850 [NSNumber numberWithBool:YES], @"refresh",
1851 activity, @"activity",
1852 [NSNumber numberWithUnsignedInteger:10], @"retries",
1854 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1856 } else if (([node class] == [PithosSubdirNode class]) &&
1857 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
1858 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1859 dispatch_async(queue, ^{
1860 NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1861 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1862 NSString *fileName = [safeObjectName lastPathComponent];
1863 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1864 objectName:safeObjectName
1866 contentType:@"application/directory"
1868 contentDisposition:nil
1871 isPublic:ASIPithosObjectRequestPublicIgnore
1873 data:[NSData data]];
1874 objectRequest.delegate = self;
1875 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
1876 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
1877 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1878 message:[NSString stringWithFormat:@"Creating directory '%@'", fileName]];
1879 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1880 fileName, @"fileName",
1881 [NSArray arrayWithObject:node], @"refreshNodes",
1882 [NSNumber numberWithBool:YES], @"refresh",
1883 activity, @"activity",
1884 [NSNumber numberWithUnsignedInteger:10], @"retries",
1886 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1891 - (void)menuGetInfo:(NSMenuItem *)sender {
1892 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1893 [node showPithosNodeInfo:sender];
1897 - (void)menuDelete:(NSMenuItem *)sender {
1898 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1899 if (([node class] == [PithosObjectNode class]) ||
1900 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1901 NSString *fileName = [node.pithosObject.name lastPathComponent];
1902 if ([node.pithosObject.name hasSuffix:@"/"])
1903 fileName = [fileName stringByAppendingString:@"/"];
1904 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1905 objectName:node.pithosObject.name];
1906 objectRequest.delegate = self;
1907 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1908 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1909 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1910 message:[NSString stringWithFormat:@"Deleting '%@'", fileName]];
1911 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1912 fileName, @"fileName",
1913 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
1914 activity, @"activity",
1915 [NSNumber numberWithUnsignedInteger:10], @"retries",
1917 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1918 } else if ([node class] == [PithosSubdirNode class]) {
1919 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1920 dispatch_async(queue, ^{
1921 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1922 objectName:node.pithosObject.name];
1923 if (objectRequests) {
1924 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1925 objectRequest.delegate = self;
1926 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1927 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1928 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1929 message:[NSString stringWithFormat:@"Deleting '%@'",
1930 [objectRequest.userInfo objectForKey:@"fileName"]]];
1931 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1932 [NSDictionary dictionaryWithObjectsAndKeys:
1933 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
1934 activity, @"activity",
1935 [NSNumber numberWithUnsignedInteger:10], @"retries",
1937 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1945 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1946 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1947 if (([node class] == [PithosObjectNode class]) ||
1948 (([node class] == [PithosSubdirNode class]) &&
1949 !node.pithosObject.subdir &&
1950 [node.pithosObject.name hasSuffix:@"/"])) {
1951 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1952 dispatch_async(queue, ^{
1953 NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash"
1954 objectName:node.pithosObject.name];
1955 if (safeObjectName) {
1956 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1957 objectName:node.pithosObject.name
1958 destinationContainerName:@"trash"
1959 destinationObjectName:safeObjectName
1961 if (objectRequest) {
1962 objectRequest.delegate = self;
1963 objectRequest.didFinishSelector = @selector(moveFinished:);
1964 objectRequest.didFailSelector = @selector(moveFailed:);
1965 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1966 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1967 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1968 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1969 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1970 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1971 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1972 [NSDictionary dictionaryWithObjectsAndKeys:
1973 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
1974 activity, @"activity",
1975 [NSNumber numberWithUnsignedInteger:10], @"retries",
1977 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1981 } else if ([node class] == [PithosSubdirNode class]) {
1982 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1983 dispatch_async(queue, ^{
1984 NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:@"trash"
1985 subdirName:node.pithosObject.name];
1986 if (safeObjectName) {
1987 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1988 objectName:node.pithosObject.name
1989 destinationContainerName:@"trash"
1990 destinationObjectName:safeObjectName
1992 if (objectRequests) {
1993 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1994 objectRequest.delegate = self;
1995 objectRequest.didFinishSelector = @selector(moveFinished:);
1996 objectRequest.didFailSelector = @selector(moveFailed:);
1997 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1998 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1999 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2000 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2001 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2002 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
2003 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2004 [NSDictionary dictionaryWithObjectsAndKeys:
2005 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2006 activity, @"activity",
2007 [NSNumber numberWithUnsignedInteger:10], @"retries",
2009 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
2018 - (void)menuCut:(NSMenuItem *)sender {
2019 self.clipboardNodes = (NSArray *)[sender representedObject];
2020 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2021 self.clipboardCopy = NO;
2024 - (void)menuCopy:(NSMenuItem *)sender {
2025 self.clipboardNodes = (NSArray *)[sender representedObject];
2026 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2027 self.clipboardCopy = YES;
2030 - (void)menuPaste:(NSMenuItem *)sender {
2031 if (!clipboardNodes || ![clipboardNodes count])
2033 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2034 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2035 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2036 self.clipboardNodes = nil;
2037 self.clipboardParentNode = nil;
2038 [self moveNodes:localClipboardNodes toNode:dropNode];
2040 [self copyNodes:localClipboardNodes toNode:dropNode];
2045 #pragma mark PithosActivityFacilityDelegate
2047 - (void)activityUpdate:(NSDictionary *)info {
2048 NSString *message = [info objectForKey:@"message"];
2049 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2050 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2051 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2052 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2053 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2054 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2055 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2056 if (runningActivitiesCount && totalBytes) {
2057 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2058 [activityProgressIndicator startAnimation:self];
2060 [activityProgressIndicator setDoubleValue:1.0];
2061 [activityProgressIndicator stopAnimation:self];
2065 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2066 [activityTextField setStringValue:message];