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;
1093 NSUInteger currentBytes = activity.currentBytes;
1095 // XXX change contentLength to objectContentLength if it is fixed in the server
1096 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
1097 NSLog(@"Downloaded 0 bytes");
1098 NSFileManager *fileManager = [NSFileManager defaultManager];
1099 if (![fileManager fileExistsAtPath:filePath]) {
1100 if (![fileManager createFileAtPath:filePath contents:nil attributes:nil]) {
1101 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1102 [alert setMessageText:@"Create File Error"];
1103 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
1104 [alert addButtonWithTitle:@"OK"];
1110 currentBytes = [objectRequest objectContentLength];
1111 if (currentBytes == 0)
1112 currentBytes = totalBytes;
1113 [activityFacility endActivity:activity
1114 withMessage:[NSString stringWithFormat:@"Downloading '%@' (100%%)",
1115 [objectRequest.userInfo objectForKey:@"fileName"]]
1116 totalBytes:totalBytes
1117 currentBytes:currentBytes];
1119 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1121 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1122 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1123 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1125 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1126 withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)",
1127 [objectRequest.userInfo objectForKey:@"fileName"]]];
1128 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1133 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1134 NSLog(@"Download failed: %@", objectRequest.url);
1135 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1137 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1138 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1139 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1141 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1142 withMessage:[NSString stringWithFormat:@"Downloading '%@' (failed)",
1143 [objectRequest.userInfo objectForKey:@"fileName"]]];
1144 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1148 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1149 NSLog(@"Upload directory object finished: %@", objectRequest.url);
1150 if (objectRequest.responseStatusCode == 201) {
1151 NSLog(@"Directory object created: %@", objectRequest.url);
1152 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1153 withMessage:[NSString stringWithFormat:@"Creating directory '%@' (finished)",
1154 [objectRequest.userInfo objectForKey:@"fileName"]]];
1155 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1156 [node forceRefresh];
1158 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1161 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1162 [self forceRefresh:self];
1163 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1164 [self refresh:self];
1166 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1168 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1169 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1170 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1172 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1173 withMessage:[NSString stringWithFormat:@"Creating directory '%@' (failed)",
1174 [objectRequest.userInfo objectForKey:@"fileName"]]];
1175 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1180 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1181 NSLog(@"Upload directory object failed: %@", objectRequest.url);
1182 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1184 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1185 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1186 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1188 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1189 withMessage:[NSString stringWithFormat:@"Creating directory '%@' (failed)",
1190 [objectRequest.userInfo objectForKey:@"fileName"]]];
1191 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1195 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
1196 NSLog(@"Upload using hashmap finished: %@", objectRequest.url);
1197 NSString *fileName = [objectRequest.userInfo objectForKey:@"fileName"];
1198 PithosActivity *activity = [objectRequest.userInfo objectForKey:@"activity"];
1199 NSUInteger totalBytes = activity.totalBytes;
1200 NSUInteger currentBytes = activity.currentBytes;
1201 if (objectRequest.responseStatusCode == 201) {
1202 NSLog(@"Object created: %@", objectRequest.url);
1203 [activityFacility endActivity:activity
1204 withMessage:[NSString stringWithFormat:@"Uploading '%@' (100%%)", fileName]
1205 totalBytes:totalBytes
1206 currentBytes:totalBytes];
1207 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1208 [node forceRefresh];
1210 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1213 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1214 [self forceRefresh:self];
1215 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1216 [self refresh:self];
1217 } else if (objectRequest.responseStatusCode == 409) {
1218 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue];
1219 if (iteration == 0) {
1220 NSLog(@"Upload iteration limit reached: %@", objectRequest.url);
1221 [activityFacility endActivity:activity
1222 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1223 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
1224 [alert setMessageText:@"Upload Timeout"];
1225 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
1226 [objectRequest.userInfo objectForKey:@"objectName"]]];
1227 [alert addButtonWithTitle:@"OK"];
1231 NSLog(@"object is missing hashes: %@", objectRequest.url);
1232 NSIndexSet *missingBlocks = [PithosUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
1233 withMissingHashesResponse:[objectRequest responseString]];
1234 NSUInteger blockSize = [[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1235 if (totalBytes >= [missingBlocks count]*blockSize)
1236 currentBytes = totalBytes - [missingBlocks count]*blockSize;
1237 [activityFacility updateActivity:activity
1238 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
1239 totalBytes:totalBytes
1240 currentBytes:currentBytes];
1241 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
1242 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
1244 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
1245 missingBlockIndex:missingBlockIndex
1246 sharingAccount:[objectRequest.userInfo objectForKey:@"sharingAccount"]];
1247 newContainerRequest.delegate = self;
1248 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1249 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1250 newContainerRequest.userInfo = objectRequest.userInfo;
1251 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:(--iteration)] forKey:@"iteration"];
1252 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1253 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
1254 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1255 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1256 [activityFacility updateActivity:activity
1257 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1258 totalBytes:activity.totalBytes
1259 currentBytes:(activity.currentBytes + size)];
1261 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1263 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1265 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1266 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1267 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1269 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1270 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1271 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1276 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
1277 NSLog(@"Upload using hashmap failed: %@", objectRequest.url);
1278 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1280 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1281 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1282 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1284 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1285 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)",
1286 [objectRequest.userInfo objectForKey:@"fileName"]]];
1287 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1291 - (void)uploadMissingBlockFinished:(ASIPithosContainerRequest *)containerRequest {
1292 NSLog(@"Upload of missing block finished: %@", containerRequest.url);
1293 NSUInteger blockSize = [[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue];
1294 NSString *fileName = [containerRequest.userInfo objectForKey:@"fileName"];
1295 PithosActivity *activity = [containerRequest.userInfo objectForKey:@"activity"];
1296 NSUInteger totalBytes = activity.totalBytes;
1297 NSUInteger currentBytes = activity.currentBytes + blockSize;
1298 if (currentBytes > totalBytes)
1299 currentBytes = totalBytes;
1300 if (containerRequest.responseStatusCode == 202) {
1301 [activityFacility updateActivity:activity
1302 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(currentBytes + 0.0)/(totalBytes + 0.0))]
1303 totalBytes:totalBytes
1304 currentBytes:currentBytes];
1305 NSIndexSet *missingBlocks = [containerRequest.userInfo objectForKey:@"missingBlocks"];
1306 NSUInteger missingBlockIndex = [[containerRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
1307 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
1308 if (missingBlockIndex == NSNotFound) {
1309 NSArray *hashes = [containerRequest.userInfo objectForKey:@"hashes"];
1310 ASIPithosObjectRequest *newObjectRequest = [PithosUtilities writeObjectDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1311 objectName:[containerRequest.userInfo objectForKey:@"objectName"]
1312 contentType:[containerRequest.userInfo objectForKey:@"contentType"]
1314 blockHash:[containerRequest.userInfo objectForKey:@"blockHash"]
1315 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1318 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1319 newObjectRequest.delegate = self;
1320 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
1321 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
1322 newObjectRequest.userInfo = containerRequest.userInfo;
1323 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1324 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
1325 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
1326 [[PithosUtilities prepareRequest:newObjectRequest] startAsynchronous];
1328 __block ASIPithosContainerRequest *newContainerRequest = [PithosUtilities updateContainerDataRequestWithContainerName:[containerRequest.userInfo objectForKey:@"containerName"]
1329 blockSize:[[containerRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
1330 forFile:[containerRequest.userInfo objectForKey:@"filePath"]
1331 missingBlockIndex:missingBlockIndex
1332 sharingAccount:[containerRequest.userInfo objectForKey:@"sharingAccount"]];
1333 newContainerRequest.delegate = self;
1334 newContainerRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
1335 newContainerRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
1336 newContainerRequest.userInfo = containerRequest.userInfo;
1337 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:10] forKey:@"retries"];
1338 [(NSMutableDictionary *)(newContainerRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
1339 [newContainerRequest setBytesSentBlock:^(unsigned long long size, unsigned long long total){
1340 [activityFacility updateActivity:activity
1341 withMessage:[NSString stringWithFormat:@"Uploading '%@' (%.0f%%)", fileName, (100*(activity.currentBytes + size + 0.0)/(activity.totalBytes + 0.0))]
1342 totalBytes:activity.totalBytes
1343 currentBytes:(activity.currentBytes + size)];
1345 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1348 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1350 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
1351 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1352 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1354 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1355 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)", fileName]];
1356 [PithosUtilities unexpectedResponseStatusAlertWithRequest:containerRequest];
1361 - (void)uploadMissingBlockFailed:(ASIPithosContainerRequest *)containerRequest {
1362 NSLog(@"Upload of missing block failed: %@", containerRequest.url);
1363 NSUInteger retries = [[containerRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1365 ASIPithosContainerRequest *newContainerRequest = (ASIPithosContainerRequest *)[PithosUtilities copyRequest:containerRequest];
1366 [(NSMutableDictionary *)(newContainerRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1367 [[PithosUtilities prepareRequest:newContainerRequest] startAsynchronous];
1369 [activityFacility endActivity:[containerRequest.userInfo objectForKey:@"activity"]
1370 withMessage:[NSString stringWithFormat:@"Uploading '%@' (failed)",
1371 [containerRequest.userInfo objectForKey:@"fileName"]]];
1372 [PithosUtilities httpRequestErrorAlertWithRequest:containerRequest];
1376 - (void)moveFinished:(ASIPithosObjectRequest *)objectRequest {
1377 NSLog(@"Move object finished: %@", objectRequest.url);
1378 if (objectRequest.responseStatusCode == 201) {
1379 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1380 withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (finished)",
1381 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1382 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1383 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1384 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1385 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1386 [node forceRefresh];
1388 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1391 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1392 [self forceRefresh:self];
1393 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1394 [self refresh:self];
1396 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1398 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1399 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1400 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1402 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1403 withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (failed)",
1404 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1405 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1406 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1407 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1408 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1413 - (void)moveFailed:(ASIPithosObjectRequest *)objectRequest {
1414 NSLog(@"Move object failed: %@", objectRequest.url);
1415 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1417 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1418 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1419 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1421 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1422 withMessage:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@' (failed)",
1423 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1424 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1425 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1426 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1427 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1431 - (void)copyFinished:(ASIPithosObjectRequest *)objectRequest {
1432 NSLog(@"Copy object finished: %@", objectRequest.url);
1433 if (objectRequest.responseStatusCode == 201) {
1434 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1435 withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (finished)",
1436 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1437 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1438 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1439 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1440 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1441 [node forceRefresh];
1443 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1446 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1447 [self forceRefresh:self];
1448 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1449 [self refresh:self];
1451 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1453 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1454 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1455 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1457 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1458 withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (failed)",
1459 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1460 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1461 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1462 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1463 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1468 - (void)copyFailed:(ASIPithosObjectRequest *)objectRequest {
1469 NSLog(@"Copy object failed: %@", objectRequest.url);
1470 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1472 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1473 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1474 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1476 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1477 withMessage:[NSString stringWithFormat:@"Copying '%@/%@' to '%@/%@' (failed)",
1478 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1479 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1480 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1481 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1482 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1486 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
1487 NSLog(@"Delete object finished: %@", objectRequest.url);
1488 if (objectRequest.responseStatusCode == 204) {
1489 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1490 withMessage:[NSString stringWithFormat:@"Deleting '%@' (finished)",
1491 [objectRequest.userInfo objectForKey:@"fileName"]]];
1492 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"forceRefreshNodes"]) {
1493 [node forceRefresh];
1495 for (PithosNode *node in [objectRequest.userInfo objectForKey:@"refreshNodes"]) {
1498 if ([[objectRequest.userInfo objectForKey:@"forceRefresh"] boolValue])
1499 [self forceRefresh:self];
1500 else if ([[objectRequest.userInfo objectForKey:@"refresh"] boolValue])
1501 [self refresh:self];
1503 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1505 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1506 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1507 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1509 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1510 withMessage:[NSString stringWithFormat:@"Deleting '%@' (failed)",
1511 [objectRequest.userInfo objectForKey:@"fileName"]]];
1512 [PithosUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
1517 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
1518 NSLog(@"Delete object failed: %@", objectRequest.url);
1519 NSUInteger retries = [[objectRequest.userInfo objectForKey:@"retries"] unsignedIntegerValue];
1521 ASIPithosObjectRequest *newObjectRequest = (ASIPithosObjectRequest *)[PithosUtilities copyRequest:objectRequest];
1522 [(NSMutableDictionary *)(newObjectRequest.userInfo)setObject:[NSNumber numberWithUnsignedInteger:(--retries)] forKey:@"retries"];
1523 [[PithosUtilities prepareRequest:newObjectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1525 [activityFacility endActivity:[objectRequest.userInfo objectForKey:@"activity"]
1526 withMessage:[NSString stringWithFormat:@"Deleting '%@' (failed)",
1527 [objectRequest.userInfo objectForKey:@"fileName"]]];
1528 [PithosUtilities httpRequestErrorAlertWithRequest:objectRequest];
1533 #pragma mark NSSplitViewDelegate
1535 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
1536 if (splitView == verticalSplitView)
1539 return ([horizontalSplitView bounds].size.height - 108);
1542 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
1543 if (splitView == verticalSplitView)
1546 return ([horizontalSplitView bounds].size.height - 108);
1549 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
1550 if (splitView == verticalSplitView) {
1551 if (proposedPosition < 120)
1553 else if (proposedPosition > 220)
1556 return proposedPosition;
1558 return ([horizontalSplitView bounds].size.height - 108);
1563 #pragma mark NSOutlineViewDataSource
1565 - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
1568 if (item == containersNode)
1569 return containersNodeChildren.count;
1570 if (item == sharedNode)
1575 - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
1577 return (!index ? containersNode : sharedNode);
1578 if (item == sharedNode)
1579 return (!index ? mySharedNode : othersSharedNode);
1580 return [containersNodeChildren objectAtIndex:index];
1583 - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
1584 if ((item == containersNode) || (item == sharedNode))
1589 - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
1590 PithosNode *node = (PithosNode *)item;
1594 #pragma mark Drag and Drop destination
1596 - (NSDragOperation)outlineView:(NSOutlineView *)anOutlineView
1597 validateDrop:(id<NSDraggingInfo>)info
1598 proposedItem:(id)item
1599 proposedChildIndex:(NSInteger)index {
1600 NSDragOperation result = NSDragOperationNone;
1601 if ((item == nil) || (index != NSOutlineViewDropOnItemIndex))
1603 PithosNode *dropNode = (PithosNode *)item;
1604 if ([dropNode class] != [PithosContainerNode class])
1606 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1607 result = NSDragOperationCopy;
1608 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1609 if ([info draggingSourceOperationMask] & NSDragOperationMove) {
1610 // NSDragOperationCopy | NSDragOperationMove -> NSDragOperationMove
1611 if (![dropNode isEqualTo:draggedParentNode])
1612 result = NSDragOperationMove;
1613 } else if ([info draggingSourceOperationMask] & NSDragOperationCopy) {
1614 // NSDragOperationCopy (Option modifier) -> NSDragOperationCopy
1615 result = NSDragOperationCopy;
1621 - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id<NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index {
1622 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
1623 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
1624 NSLog(@"drag in operation: %lu filenames: %@", [info draggingSourceOperationMask], filenames);
1625 if (item && (index == NSOutlineViewDropOnItemIndex) && (filenames != nil)) {
1626 PithosNode *node = (PithosNode *)item;
1627 NSLog(@"drag in node: %@", node.url);
1628 return [self uploadFiles:filenames toNode:node];
1630 } else if ([[[info draggingPasteboard] types] containsObject:NSFilesPromisePboardType]) {
1631 NSLog(@"drag local operation: %lu nodes: %@", [info draggingSourceOperationMask], draggedNodes);
1632 if (item && (index == NSOutlineViewDropOnItemIndex) && (draggedNodes != nil)) {
1633 PithosNode *node = (PithosNode *)item;
1634 NSLog(@"drag local node: %@", node.url);
1635 if ([info draggingSourceOperationMask] & NSDragOperationMove)
1636 return [self moveNodes:draggedNodes toNode:node];
1637 else if ([info draggingSourceOperationMask] & NSDragOperationCopy)
1638 return [self copyNodes:draggedNodes toNode:node];
1645 #pragma mark NSOutlineViewDelegate
1647 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
1648 if ((item == containersNode) || (item == sharedNode))
1653 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
1654 if ((item == containersNode) || (item == sharedNode))
1659 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
1660 PithosNode *node = (PithosNode *)[outlineView itemAtRow:[outlineView selectedRow]];
1663 [browser loadColumnZero];
1669 #pragma mark NSMenuDelegate
1671 - (void)menuNeedsUpdate:(NSMenu *)menu {
1672 [menu removeAllItems];
1673 NSMenuItem *menuItem;
1674 NSString *menuItemTitle;
1675 BOOL nodeContextMenu = NO;
1676 PithosNode *menuNode;
1677 NSMutableArray *menuNodes;
1678 if (menu == browserMenu) {
1679 NSInteger column = [browser clickedColumn];
1680 NSInteger row = [browser clickedRow];
1681 if ((column == -1) || (row == -1)) {
1682 // General context menu
1683 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1684 if ([menuNodesIndexPaths count] == 0) {
1685 menuNode = [browser parentForItemsInColumn:0];
1686 } else if (([menuNodesIndexPaths count] != 1) ||
1687 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
1688 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
1690 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
1693 // Node context menu
1694 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
1695 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
1696 menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
1697 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
1698 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
1699 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
1702 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
1704 nodeContextMenu = YES;
1706 } else if (menu == outlineViewMenu) {
1707 NSInteger row = [outlineView clickedRow];
1709 row = [outlineView selectedRow];
1712 menuNode = [outlineView itemAtRow:row];
1715 if (!nodeContextMenu) {
1716 // General context menu
1717 if (([menuNode class] == [PithosAccountNode class]) ||
1718 ([menuNode class] == [PithosSharingAccountsNode class]) ||
1719 ([menuNode class] == [PithosEmptyNode class]))
1721 BOOL shared = menuNode.shared;
1722 BOOL sharingAccount = (menuNode.sharingAccount != nil);
1724 if (!shared && !sharingAccount) {
1725 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(menuNewFolder:) keyEquivalent:@""] autorelease];
1726 [menuItem setRepresentedObject:menuNode];
1727 [menu addItem:menuItem];
1728 [menu addItem:[NSMenuItem separatorItem]];
1731 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1732 [menuItem setRepresentedObject:[NSArray arrayWithObject:menuNode]];
1733 [menu addItem:menuItem];
1735 if (!shared && !sharingAccount) {
1736 if (clipboardNodes) {
1737 NSUInteger clipboardNodesCount = [clipboardNodes count];
1738 if (clipboardNodesCount == 0) {
1739 self.clipboardNodes = nil;
1740 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1741 if (clipboardNodesCount == 1)
1742 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1744 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1745 [menu addItem:[NSMenuItem separatorItem]];
1746 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1747 [menuItem setRepresentedObject:menuNode];
1748 [menu addItem:menuItem];
1753 // Node context menu
1754 NSUInteger menuNodesCount = [menuNodes count];
1755 PithosNode *firstMenuNode = (PithosNode *)[menuNodes objectAtIndex:0];
1756 BOOL shared = firstMenuNode.shared;
1757 BOOL sharingAccount = (firstMenuNode.sharingAccount != nil);
1758 // Move to Trash (pithos container only)
1760 if (!shared && !sharingAccount) {
1761 if ([rootNode class] == [PithosContainerNode class]) {
1762 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
1763 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(menuMoveToTrash:) keyEquivalent:@""] autorelease];
1764 [menuItem setRepresentedObject:menuNodes];
1765 [menu addItem:menuItem];
1767 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(menuDelete:) keyEquivalent:@""] autorelease];
1768 [menuItem setRepresentedObject:menuNodes];
1769 [menu addItem:menuItem];
1770 [menu addItem:[NSMenuItem separatorItem]];
1774 if (!sharingAccount || ([firstMenuNode class] != [PithosAccountNode class])) {
1775 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(menuGetInfo:) keyEquivalent:@""] autorelease];
1776 [menuItem setRepresentedObject:menuNodes];
1777 [menu addItem:menuItem];
1779 if ((!shared && !sharingAccount) || ([firstMenuNode class] != [PithosContainerNode class]))
1780 [menu addItem:[NSMenuItem separatorItem]];
1783 if (!shared && !sharingAccount) {
1784 if (menuNodesCount == 1)
1785 menuItemTitle = [NSString stringWithFormat:@"Cut \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1787 menuItemTitle = [NSString stringWithFormat:@"Cut %lu Items", menuNodesCount];
1788 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCut:) keyEquivalent:@""] autorelease];
1789 [menuItem setRepresentedObject:menuNodes];
1790 [menu addItem:menuItem];
1793 if ((!shared && !sharingAccount) ||
1794 (([firstMenuNode class] != [PithosContainerNode class]) && ([firstMenuNode class] != [PithosAccountNode class]))) {
1795 if (menuNodesCount == 1)
1796 menuItemTitle = [NSString stringWithFormat:@"Copy \"%@\"", ((PithosNode *)[menuNodes objectAtIndex:0]).displayName];
1798 menuItemTitle = [NSString stringWithFormat:@"Copy %lu Items", menuNodesCount];
1799 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuCopy:) keyEquivalent:@""] autorelease];
1800 [menuItem setRepresentedObject:menuNodes];
1801 [menu addItem:menuItem];
1804 if (!shared && !sharingAccount) {
1805 if (menuNodesCount == 1) {
1806 PithosNode *menuNode = [menuNodes objectAtIndex:0];
1807 if (([menuNode class] == [PithosSubdirNode class]) &&
1808 (menuNode.pithosObject.subdir || ![menuNode.pithosObject.name hasSuffix:@"/"])) {
1809 if (clipboardNodes) {
1810 NSUInteger clipboardNodesCount = [clipboardNodes count];
1811 if (clipboardNodesCount == 0) {
1812 self.clipboardNodes = nil;
1813 } else if (clipboardCopy || ![menuNode isEqualTo:clipboardParentNode]) {
1814 if (clipboardNodesCount == 1)
1815 menuItemTitle = [NSString stringWithString:@"Paste Item"];
1817 menuItemTitle = [NSString stringWithString:@"Paste Items"];
1818 menuItem = [[[NSMenuItem alloc] initWithTitle:menuItemTitle action:@selector(menuPaste:) keyEquivalent:@""] autorelease];
1819 [menuItem setRepresentedObject:menuNode];
1820 [menu addItem:menuItem];
1830 #pragma mark Menu Actions
1832 - (void)menuNewFolder:(NSMenuItem *)sender {
1833 PithosNode *node = (PithosNode *)[sender representedObject];
1834 if ([node class] == [PithosContainerNode class]) {
1835 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1836 dispatch_async(queue, ^{
1837 NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1838 subdirName:@"untitled folder"];
1839 NSString *fileName = [safeObjectName lastPathComponent];
1840 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1841 objectName:safeObjectName
1843 contentType:@"application/directory"
1845 contentDisposition:nil
1848 isPublic:ASIPithosObjectRequestPublicIgnore
1850 data:[NSData data]];
1851 objectRequest.delegate = self;
1852 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
1853 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
1854 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1855 message:[NSString stringWithFormat:@"Creating directory '%@'", fileName]];
1856 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1857 fileName, @"fileName",
1858 [NSArray arrayWithObject:node], @"refreshNodes",
1859 [NSNumber numberWithBool:YES], @"refresh",
1860 activity, @"activity",
1861 [NSNumber numberWithUnsignedInteger:10], @"retries",
1863 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1865 } else if (([node class] == [PithosSubdirNode class]) &&
1866 (node.pithosObject.subdir || ![node.pithosObject.name hasSuffix:@"/"])) {
1867 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1868 dispatch_async(queue, ^{
1869 NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:node.pithosContainer.name
1870 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
1871 NSString *fileName = [safeObjectName lastPathComponent];
1872 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
1873 objectName:safeObjectName
1875 contentType:@"application/directory"
1877 contentDisposition:nil
1880 isPublic:ASIPithosObjectRequestPublicIgnore
1882 data:[NSData data]];
1883 objectRequest.delegate = self;
1884 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
1885 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
1886 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityCreateDirectory
1887 message:[NSString stringWithFormat:@"Creating directory '%@'", fileName]];
1888 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1889 fileName, @"fileName",
1890 [NSArray arrayWithObject:node], @"refreshNodes",
1891 [NSNumber numberWithBool:YES], @"refresh",
1892 activity, @"activity",
1893 [NSNumber numberWithUnsignedInteger:10], @"retries",
1895 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1900 - (void)menuGetInfo:(NSMenuItem *)sender {
1901 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1902 [node showPithosNodeInfo:sender];
1906 - (void)menuDelete:(NSMenuItem *)sender {
1907 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1908 if (([node class] == [PithosObjectNode class]) ||
1909 (([node class] == [PithosSubdirNode class]) && !node.pithosObject.subdir && [node.pithosObject.name hasSuffix:@"/"])) {
1910 NSString *fileName = [node.pithosObject.name lastPathComponent];
1911 if ([node.pithosObject.name hasSuffix:@"/"])
1912 fileName = [fileName stringByAppendingString:@"/"];
1913 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
1914 objectName:node.pithosObject.name];
1915 objectRequest.delegate = self;
1916 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1917 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1918 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1919 message:[NSString stringWithFormat:@"Deleting '%@'", fileName]];
1920 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
1921 fileName, @"fileName",
1922 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
1923 activity, @"activity",
1924 [NSNumber numberWithUnsignedInteger:10], @"retries",
1926 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1927 } else if ([node class] == [PithosSubdirNode class]) {
1928 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1929 dispatch_async(queue, ^{
1930 NSArray *objectRequests = [PithosUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1931 objectName:node.pithosObject.name];
1932 if (objectRequests) {
1933 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
1934 objectRequest.delegate = self;
1935 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
1936 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
1937 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityDelete
1938 message:[NSString stringWithFormat:@"Deleting '%@'",
1939 [objectRequest.userInfo objectForKey:@"fileName"]]];
1940 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1941 [NSDictionary dictionaryWithObjectsAndKeys:
1942 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
1943 activity, @"activity",
1944 [NSNumber numberWithUnsignedInteger:10], @"retries",
1946 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1954 - (void)menuMoveToTrash:(NSMenuItem *)sender {
1955 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
1956 if (([node class] == [PithosObjectNode class]) ||
1957 (([node class] == [PithosSubdirNode class]) &&
1958 !node.pithosObject.subdir &&
1959 [node.pithosObject.name hasSuffix:@"/"])) {
1960 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1961 dispatch_async(queue, ^{
1962 NSString *safeObjectName = [PithosUtilities safeObjectNameForContainerName:@"trash"
1963 objectName:node.pithosObject.name];
1964 if (safeObjectName) {
1965 ASIPithosObjectRequest *objectRequest = [PithosUtilities moveObjectRequestWithContainerName:node.pithosContainer.name
1966 objectName:node.pithosObject.name
1967 destinationContainerName:@"trash"
1968 destinationObjectName:safeObjectName
1970 if (objectRequest) {
1971 objectRequest.delegate = self;
1972 objectRequest.didFinishSelector = @selector(moveFinished:);
1973 objectRequest.didFailSelector = @selector(moveFailed:);
1974 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
1975 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
1976 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
1977 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
1978 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
1979 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
1980 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
1981 [NSDictionary dictionaryWithObjectsAndKeys:
1982 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
1983 activity, @"activity",
1984 [NSNumber numberWithUnsignedInteger:10], @"retries",
1986 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
1990 } else if ([node class] == [PithosSubdirNode class]) {
1991 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
1992 dispatch_async(queue, ^{
1993 NSString *safeObjectName = [PithosUtilities safeSubdirNameForContainerName:@"trash"
1994 subdirName:node.pithosObject.name];
1995 if (safeObjectName) {
1996 NSArray *objectRequests = [PithosUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
1997 objectName:node.pithosObject.name
1998 destinationContainerName:@"trash"
1999 destinationObjectName:safeObjectName
2001 if (objectRequests) {
2002 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
2003 objectRequest.delegate = self;
2004 objectRequest.didFinishSelector = @selector(moveFinished:);
2005 objectRequest.didFailSelector = @selector(moveFailed:);
2006 PithosActivity *activity = [activityFacility startActivityWithType:PithosActivityMove
2007 message:[NSString stringWithFormat:@"Moving '%@/%@' to '%@/%@'",
2008 [objectRequest.userInfo objectForKey:@"sourceContainerName"],
2009 [objectRequest.userInfo objectForKey:@"sourceObjectName"],
2010 [objectRequest.userInfo objectForKey:@"destinationContainerName"],
2011 [objectRequest.userInfo objectForKey:@"destinationObjectName"]]];
2012 [(NSMutableDictionary *)(objectRequest.userInfo) addEntriesFromDictionary:
2013 [NSDictionary dictionaryWithObjectsAndKeys:
2014 [NSArray arrayWithObject:node.parent], @"forceRefreshNodes",
2015 activity, @"activity",
2016 [NSNumber numberWithUnsignedInteger:10], @"retries",
2018 [[PithosUtilities prepareRequest:objectRequest priority:NSOperationQueuePriorityVeryHigh] startAsynchronous];
2027 - (void)menuCut:(NSMenuItem *)sender {
2028 self.clipboardNodes = (NSArray *)[sender representedObject];
2029 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2030 self.clipboardCopy = NO;
2033 - (void)menuCopy:(NSMenuItem *)sender {
2034 self.clipboardNodes = (NSArray *)[sender representedObject];
2035 self.clipboardParentNode = ((PithosNode *)[clipboardNodes objectAtIndex:0]).parent;
2036 self.clipboardCopy = YES;
2039 - (void)menuPaste:(NSMenuItem *)sender {
2040 if (!clipboardNodes || ![clipboardNodes count])
2042 PithosNode *dropNode = (PithosNode *)[sender representedObject];
2043 NSArray *localClipboardNodes = [NSArray arrayWithArray:clipboardNodes];
2044 if (!clipboardCopy && ![dropNode isEqualTo:clipboardParentNode]) {
2045 self.clipboardNodes = nil;
2046 self.clipboardParentNode = nil;
2047 [self moveNodes:localClipboardNodes toNode:dropNode];
2049 [self copyNodes:localClipboardNodes toNode:dropNode];
2054 #pragma mark PithosActivityFacilityDelegate
2056 - (void)activityUpdate:(NSDictionary *)info {
2057 NSString *message = [info objectForKey:@"message"];
2058 NSUInteger runningActivitiesCount = [[info objectForKey:@"runningActivitiesCount"] unsignedIntegerValue];
2059 // NSUInteger endingActivitiesCount = [[info objectForKey:@"endingActivitiesCount"] unsignedIntegerValue];
2060 NSUInteger totalUploadBytes = [[info objectForKey:@"totalUploadBytes"] unsignedIntegerValue];
2061 NSUInteger currentUploadBytes = [[info objectForKey:@"currentUploadBytes"] unsignedIntegerValue];
2062 NSUInteger totalDownloadBytes = [[info objectForKey:@"totalDownloadBytes"] unsignedIntegerValue];
2063 NSUInteger currentDownloadBytes = [[info objectForKey:@"currentDownloadBytes"] unsignedIntegerValue];
2064 NSUInteger totalBytes = totalUploadBytes + totalDownloadBytes;
2065 if (runningActivitiesCount && totalBytes) {
2066 [activityProgressIndicator setDoubleValue:((currentUploadBytes + currentDownloadBytes + 0.0)/(totalBytes + 0.0))];
2067 [activityProgressIndicator startAnimation:self];
2069 [activityProgressIndicator setDoubleValue:1.0];
2070 [activityProgressIndicator stopAnimation:self];
2074 message = [[[[UsingSizeTransformer alloc] init] autorelease] transformedValue:accountNode.pithosAccount];
2075 [activityTextField setStringValue:message];