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 "PithosEmptyNode.h"
45 #import "ImageAndTextCell.h"
46 #import "FileSystemBrowserCell.h"
47 #import "ASIPithosRequest.h"
48 #import "ASIPithosContainerRequest.h"
49 #import "ASIPithosObjectRequest.h"
50 #import "ASIPithosContainer.h"
51 #import "ASIPithosObject.h"
52 #import "PithosFileUtilities.h"
54 @interface PithosBrowserCell : FileSystemBrowserCell {}
57 @implementation PithosBrowserCell
60 if ((self = [super init])) {
61 [self setLineBreakMode:NSLineBreakByTruncatingMiddle];
66 - (void)setObjectValue:(id)object {
67 if ([object isKindOfClass:[PithosNode class]]) {
68 PithosNode *node = (PithosNode *)object;
69 [self setStringValue:node.displayName];
70 [self setImage:node.icon];
72 [super setObjectValue:object];
78 @interface PithosOutlineViewCell : ImageAndTextCell {}
81 @implementation PithosOutlineViewCell
83 - (void)setObjectValue:(id)object {
84 if ([object isKindOfClass:[PithosNode class]]) {
85 PithosNode *node = (PithosNode *)object;
86 [self setStringValue:node.displayName];
87 [self setImage:node.icon];
88 [self setEditable:NO];
90 [super setObjectValue:object];
96 @interface PithosBrowserController (Private) {}
97 - (void)resetContainers:(NSNotification *)notification;
98 - (void)getInfo:(NSMenuItem *)sender;
99 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest;
100 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest;
101 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest;
102 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest;
103 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest;
104 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest;
107 @implementation PithosBrowserController
108 @synthesize outlineViewDataSourceArray, splitView, outlineView, browser;
111 #pragma Object Lifecycle
114 return [super initWithWindowNibName:@"PithosBrowserController"];
118 [[NSNotificationCenter defaultCenter] removeObserver:self];
119 [browserMenu release];
120 [sharedPreviewController release];
121 [outlineViewDataSourceArray release];
122 [accountNode release];
127 - (void)awakeFromNib {
128 [super awakeFromNib];
130 [browser registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
131 [browser setDraggingSourceOperationMask:NSDragOperationNone forLocal:YES];
132 [browser setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
134 [browser setCellClass:[PithosBrowserCell class]];
136 browserMenu = [[NSMenu alloc] init];
137 [browserMenu setDelegate:self];
138 [browser setMenu:browserMenu];
141 - (void)resetContainers:(NSNotification *)notification {
143 [browser loadColumnZero];
144 self.outlineViewDataSourceArray = nil;
146 // Create the outlineView tree
148 NSTreeNode *containersTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
149 [[[PithosEmptyNode alloc] initWithDisplayName:@"CONTAINERS" icon:nil] autorelease]];
152 NSTreeNode *sharedTreeNode = [NSTreeNode treeNodeWithRepresentedObject:
153 [[[PithosEmptyNode alloc] initWithDisplayName:@"SHARED" icon:nil] autorelease]];
155 [[sharedTreeNode mutableChildNodes] addObject:
156 [NSTreeNode treeNodeWithRepresentedObject:
157 [[[PithosEmptyNode alloc] initWithDisplayName:@"my shared"
158 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kUserIcon)]
160 // SHARED/others shared
161 [[sharedTreeNode mutableChildNodes] addObject:
162 [NSTreeNode treeNodeWithRepresentedObject:
163 [[[PithosEmptyNode alloc] initWithDisplayName:@"others shared"
164 icon:[[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kGroupIcon)]
167 self.outlineViewDataSourceArray = [NSMutableArray arrayWithObjects:containersTreeNode, sharedTreeNode, nil];
169 // Expand the folder outline view
170 [outlineView expandItem:nil expandChildren:YES];
171 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
173 // Create accountNode and trigger a refresh
174 accountNode = [[PithosAccountNode alloc] init];
175 accountNode.children;
178 - (void)windowDidLoad {
179 [super windowDidLoad];
181 [[[outlineView tableColumns] objectAtIndex:0] setDataCell:[[[PithosOutlineViewCell alloc] init] autorelease]];
183 // Register for updates
184 [[NSNotificationCenter defaultCenter] addObserver:self
185 selector:@selector(pithosNodeChildrenUpdated:)
186 name:@"PithosContainerNodeChildrenUpdated"
188 [[NSNotificationCenter defaultCenter] addObserver:self
189 selector:@selector(pithosNodeChildrenUpdated:)
190 name:@"PithosSubdirNodeChildrenUpdated"
192 [[NSNotificationCenter defaultCenter] addObserver:self
193 selector:@selector(pithosAccountNodeChildrenUpdated:)
194 name:@"PithosAccountNodeChildrenUpdated"
196 [[NSNotificationCenter defaultCenter] addObserver:self
197 selector:@selector(resetContainers:)
198 name:@"PithosAuthenticationCredentialsUpdated"
205 - (void)pithosNodeChildrenUpdated:(NSNotification *)notification {
206 PithosNode *node = (PithosNode *)[notification object];
207 NSLog(@"pithosNodeChildrenUpdated:%@", node.url);
208 NSInteger lastColumn = [browser lastColumn];
209 for (NSInteger column = lastColumn; column >= 0; column--) {
210 if ([[browser parentForItemsInColumn:column] isEqualTo:node]) {
211 [browser reloadColumn:column];
212 // The following code is unnecessary since the pithosObject is set in the PithosNode in each refresh
213 // Furthermore it caused problems on delete, because a non-existing parent was asked for
214 //if ((column == lastColumn - 1) && ([[browser parentForItemsInColumn:lastColumn] isLeafItem])) {
215 // // This reloads the preview column
216 // [browser setLastColumn:column];
217 // [browser addColumn];
224 - (void)pithosAccountNodeChildrenUpdated:(NSNotification *)notification {
225 BOOL containerPithosFound = NO;
226 BOOL containerTrashFound = NO;
227 NSMutableArray *containersTreeNodeChildren = [NSMutableArray array];
228 for (PithosContainerNode *containerNode in accountNode.children) {
229 if ([containerNode.pithosContainer.name isEqualToString:@"pithos"]) {
230 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kToolbarHomeIcon)];
231 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:0];
232 containerPithosFound = YES;
233 } else if ([containerNode.pithosContainer.name isEqualToString:@"trash"]) {
234 containerNode.icon = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFullTrashIcon)];
235 NSUInteger insertIndex = 1;
236 if (!containerPithosFound)
238 [containersTreeNodeChildren insertObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode] atIndex:insertIndex];
239 containerTrashFound = YES;
241 [containersTreeNodeChildren addObject:[NSTreeNode treeNodeWithRepresentedObject:containerNode]];
244 BOOL refreshAccountNode = NO;
245 if (!containerPithosFound) {
247 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"pithos"];
248 [containerRequest startSynchronous];
249 if ([containerRequest error]) {
250 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
252 refreshAccountNode = YES;
255 if (!containerTrashFound) {
257 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest createOrUpdateContainerRequestWithContainerName:@"trash"];
258 [containerRequest startSynchronous];
259 if ([containerRequest error]) {
260 [PithosFileUtilities httpRequestErrorAlertWithRequest:containerRequest];
262 refreshAccountNode = YES;
265 if (refreshAccountNode) {
266 [accountNode invalidateChildren];
267 accountNode.children;
269 [[[outlineViewDataSourceArray objectAtIndex:0] mutableChildNodes] setArray:containersTreeNodeChildren];
270 self.outlineViewDataSourceArray = outlineViewDataSourceArray;
272 // Expand the folder outline view
273 [outlineView expandItem:nil expandChildren:YES];
274 [outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:1] byExtendingSelection:NO];
283 - (IBAction)refresh:(id)sender {
284 for (NSInteger column = [browser lastColumn]; column >= 0; column--) {
285 [(PithosNode *)[browser parentForItemsInColumn:column] invalidateChildren];
287 [browser validateVisibleColumns];
291 #pragma NSBrowserDelegate
293 - (id)rootItemForBrowser:(NSBrowser *)browser {
297 - (NSInteger)browser:(NSBrowser *)browser numberOfChildrenOfItem:(id)item {
298 PithosNode *node = (PithosNode *)item;
299 return node.children.count;
302 - (id)browser:(NSBrowser *)browser child:(NSInteger)index ofItem:(id)item {
303 PithosNode *node = (PithosNode *)item;
304 return [node.children objectAtIndex:index];
307 - (BOOL)browser:(NSBrowser *)browser isLeafItem:(id)item {
308 PithosNode *node = (PithosNode *)item;
309 return node.isLeafItem;
312 - (id)browser:(NSBrowser *)browser objectValueForItem:(id)item {
313 PithosNode *node = (PithosNode *)item;
317 - (NSViewController *)browser:(NSBrowser *)browser previewViewControllerForLeafItem:(id)item {
318 if (sharedPreviewController == nil)
319 sharedPreviewController = [[NSViewController alloc] initWithNibName:@"PithosBrowserPreviewController" bundle:[NSBundle bundleForClass:[self class]]];
320 return sharedPreviewController;
323 //- (CGFloat)browser:(NSBrowser *)browser shouldSizeColumn:(NSInteger)columnIndex forUserResize:(BOOL)forUserResize toWidth:(CGFloat)suggestedWidth {
324 // if (!forUserResize) {
325 // id item = [browser parentForItemsInColumn:columnIndex];
326 // if ([self browser:browser isLeafItem:item]) {
327 // suggestedWidth = 200;
330 // return suggestedWidth;
333 - (BOOL)browser:(NSBrowser *)sender isColumnValid:(NSInteger)column {
337 #pragma mark Drag and Drop source
339 - (BOOL)browser:(NSBrowser *)aBrowser writeRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column
340 toPasteboard:(NSPasteboard *)pasteboard {
341 NSMutableArray *propertyList = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
342 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
343 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
344 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
345 [propertyList addObject:[node.pithosObject.name pathExtension]];
348 [pasteboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
349 [pasteboard setPropertyList:propertyList forType:NSFilesPromisePboardType];
354 - (NSArray *)browser:(NSBrowser *)aBrowser namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
355 forDraggedRowsWithIndexes:(NSIndexSet *)rowIndexes inColumn:(NSInteger)column {
356 NSMutableArray *names = [NSMutableArray arrayWithCapacity:[rowIndexes count]];
357 NSIndexPath *baseIndexPath = [browser indexPathForColumn:column];
358 [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
359 PithosNode *node = [browser itemAtIndexPath:[baseIndexPath indexPathByAddingIndex:idx]];
361 // If the node is a subdir ask if the whole tree should be downloaded
362 if ([node class] == [PithosSubdirNode class]) {
363 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
364 [alert setMessageText:@"Download directory"];
365 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to download its contents?", node.displayName]];
366 [alert addButtonWithTitle:@"OK"];
367 [alert addButtonWithTitle:@"Cancel"];
368 NSInteger choice = [alert runModal];
369 if (choice == NSAlertFirstButtonReturn) {
370 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
371 dispatch_async(queue, ^{
372 NSArray *objectRequests = [PithosFileUtilities objectDataRequestsForSubdirWithContainerName:node.pithosContainer.name
373 objectName:node.pithosObject.name
374 toDirectory:[dropDestination path]
376 if (objectRequests) {
377 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
378 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
379 objectRequest.delegate = self;
380 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
381 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
382 [objectRequest startAsynchronous];
388 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities objectDataRequestWithContainerName:node.pithosContainer.name
389 objectName:node.pithosObject.name
390 toDirectory:[dropDestination path]
393 [names addObject:[objectRequest.userInfo valueForKey:@"fileName"]];
394 objectRequest.delegate = self;
395 objectRequest.didFinishSelector = @selector(downloadObjectFinished:);
396 objectRequest.didFailSelector = @selector(downloadObjectFailed:);
397 [objectRequest startAsynchronous];
404 #pragma mark Drag and Drop destination
406 - (NSDragOperation)browser:aBrowser
407 validateDrop:(id<NSDraggingInfo>)info
408 proposedRow:(NSInteger *)row
409 column:(NSInteger *)column
410 dropOperation:(NSBrowserDropOperation *)dropOperation {
411 NSDragOperation result = NSDragOperationNone;
412 // Files from the finder are accepted
413 if ([[[info draggingPasteboard] types] containsObject:NSFilenamesPboardType]) {
414 // For a between drop, we let the user drop "on" the parent item
415 if (*dropOperation == NSBrowserDropAbove)
417 // Only allow dropping in folders
420 PithosNode *node = [browser itemAtRow:*row inColumn:*column];
421 if ([node class] != [PithosSubdirNode class])
424 *dropOperation = NSBrowserDropOn;
425 result = NSDragOperationCopy;
428 // XXX else local file promises
432 - (BOOL)browser:(NSBrowser *)aBrowser
433 acceptDrop:(id<NSDraggingInfo>)info
435 column:(NSInteger)column
436 dropOperation:(NSBrowserDropOperation)dropOperation {
437 NSArray *filenames = [[info draggingPasteboard] propertyListForType:NSFilenamesPboardType];
438 NSLog(@"drag in filenames: %@", filenames);
439 PithosNode *node = nil;
440 if ((column != -1) && (filenames != nil)) {
442 node = [browser itemAtRow:row inColumn:column];
444 node = [browser parentForItemsInColumn:column];
445 NSLog(@"drag in node: %@", node.url);
446 if (([node class] != [PithosSubdirNode class]) && ([node class] != [PithosContainerNode class]))
449 NSFileManager *defaultManager = [NSFileManager defaultManager];
450 NSString *containerName = [NSString stringWithString:node.pithosContainer.name];
451 NSString *objectNamePrefix;
452 if ([node class] == [PithosSubdirNode class])
453 objectNamePrefix = [NSString stringWithString:node.pithosObject.name];
455 objectNamePrefix = [NSString stringWithString:@""];
456 NSUInteger blockSize = node.pithosContainer.blockSize;
457 NSString *blockHash = node.pithosContainer.blockHash;
459 for (NSString *filePath in filenames) {
461 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
464 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
465 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
466 dispatch_async(queue, ^{
467 NSError *error = nil;
468 NSString *contentType = [PithosFileUtilities contentTypeOfFile:filePath error:&error];
469 if (contentType == nil)
470 contentType = @"application/octet-stream";
472 NSLog(@"contentType detection error: %@", error);
473 NSArray *hashes = nil;
474 ASIPithosObjectRequest *objectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:containerName
475 objectName:objectName
476 contentType:contentType
483 objectRequest.delegate = self;
484 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
485 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
486 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
487 containerName, @"containerName",
488 objectName, @"objectName",
489 contentType, @"contentType",
490 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
491 blockHash, @"blockHash",
492 filePath, @"filePath",
495 [NSNumber numberWithUnsignedInteger:10], @"iteration",
497 [objectRequest startAsynchronous];
501 // Upload directory, confirm first
502 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
503 [alert setMessageText:@"Upload directory"];
504 [alert setInformativeText:[NSString stringWithFormat:@"'%@' is a directory, do you want to upload it and its contents?", filePath]];
505 [alert addButtonWithTitle:@"OK"];
506 [alert addButtonWithTitle:@"Cancel"];
507 NSInteger choice = [alert runModal];
508 if (choice == NSAlertFirstButtonReturn) {
509 NSString *objectName = [objectNamePrefix stringByAppendingPathComponent:[filePath lastPathComponent]];
510 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
511 dispatch_async(queue, ^{
512 NSMutableArray *objectNames = nil;
513 NSMutableArray *contentTypes = nil;
514 NSMutableArray *filePaths = nil;
515 NSMutableArray *hashesArrays = nil;
516 NSMutableArray *directoryObjectRequests = nil;
517 NSArray *objectRequests = [PithosFileUtilities writeObjectDataRequestsWithContainerName:containerName
518 objectName:objectName
521 forDirectory:filePath
523 objectNames:&objectNames
524 contentTypes:&contentTypes
526 hashesArrays:&hashesArrays
527 directoryObjectRequests:&directoryObjectRequests];
528 for (ASIPithosObjectRequest *objectRequest in directoryObjectRequests) {
529 objectRequest.delegate = self;
530 objectRequest.didFinishSelector = @selector(uploadDirectoryObjectFinished:);
531 objectRequest.didFailSelector = @selector(uploadDirectoryObjectFailed:);
532 [objectRequest startAsynchronous];
534 if (objectRequests) {
535 for (NSUInteger i = 0 ; i < [objectRequests count] ; i++) {
536 ASIPithosObjectRequest *objectRequest = [objectRequests objectAtIndex:i];
537 objectRequest.delegate = self;
538 objectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
539 objectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
540 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
541 containerName, @"containerName",
542 [objectNames objectAtIndex:i], @"objectName",
543 [contentTypes objectAtIndex:i], @"contentType",
544 [NSNumber numberWithUnsignedInteger:blockSize], @"blockSize",
545 blockHash, @"blockHash",
546 [filePaths objectAtIndex:i], @"filePath",
547 [hashesArrays objectAtIndex:i], @"hashes",
548 [NSNull null], @"node",
549 [NSNumber numberWithUnsignedInteger:10], @"iteration",
551 [objectRequest startAsynchronous];
567 #pragma mark ASIHTTPRequestDelegate
569 - (void)downloadObjectFinished:(ASIPithosObjectRequest *)objectRequest {
570 NSLog(@"Download completed: %@", [objectRequest url]);
571 if (objectRequest.responseStatusCode == 200) {
572 if (([objectRequest contentLength] == 0) && (![[objectRequest contentType] isEqualToString:@"application/directory"])) {
573 NSLog(@"Downloaded 0 bytes");
574 NSFileManager *defaultManager = [NSFileManager defaultManager];
575 NSString *filePath = [objectRequest.userInfo objectForKey:@"filePath"];
576 if (![defaultManager fileExistsAtPath:filePath]) {
577 if (![defaultManager createFileAtPath:filePath contents:nil attributes:nil]) {
578 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
579 [alert setMessageText:@"Create File Error"];
580 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create zero length file at %@", filePath]];
581 [alert addButtonWithTitle:@"OK"];
587 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
591 - (void)downloadObjectFailed:(ASIPithosObjectRequest *)objectRequest {
592 NSLog(@"Download failed");
593 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
596 - (void)uploadDirectoryObjectFinished:(ASIPithosObjectRequest *)objectRequest {
597 NSLog(@"Upload directory object completed: %@", [objectRequest url]);
598 if (objectRequest.responseStatusCode == 201) {
599 NSLog(@"Directory object created: %@", [objectRequest url]);
602 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
606 - (void)uploadDirectoryObjectFailed:(ASIPithosObjectRequest *)objectRequest {
607 NSLog(@"Upload directory object failed");
608 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
611 - (void)uploadObjectUsingHashMapFinished:(ASIPithosObjectRequest *)objectRequest {
612 NSLog(@"Upload using hashmap completed: %@", [objectRequest url]);
613 if (objectRequest.responseStatusCode == 201) {
614 NSLog(@"Object created: %@", [objectRequest url]);
615 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
616 if (node != (id)[NSNull null]) {
617 [node.parent invalidateChildren];
618 node.parent.children;
622 } else if (objectRequest.responseStatusCode == 409) {
623 NSUInteger iteration = [[objectRequest.userInfo objectForKey:@"iteration"] unsignedIntegerValue] - 1;
624 if (iteration == 0) {
625 NSLog(@"Upload iteration limit reached: %@", [objectRequest url]);
626 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
627 [alert setMessageText:@"Upload Timeout"];
628 [alert setInformativeText:[NSString stringWithFormat:@"Upload iteration limit reached for object with path '%@'",
629 [objectRequest.userInfo objectForKey:@"objectName"], [objectRequest.userInfo objectForKey:@"containerName"]]];
630 [alert addButtonWithTitle:@"OK"];
634 NSLog(@"object is missing hashes: %@", [objectRequest url]);
635 NSIndexSet *missingBlocks = [PithosFileUtilities missingBlocksForHashes:[objectRequest.userInfo objectForKey:@"hashes"]
636 withMissingHashesResponse:[objectRequest responseString]];
637 NSUInteger missingBlockIndex = [missingBlocks firstIndex];
638 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
639 objectName:@".upload"
640 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
641 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
642 missingBlockIndex:missingBlockIndex];
643 newObjectRequest.delegate = self;
644 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
645 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
646 newObjectRequest.userInfo = objectRequest.userInfo;
647 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:iteration] forKey:@"iteration"];
648 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:missingBlocks forKey:@"missingBlocks"];
649 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
650 [newObjectRequest startAsynchronous];
652 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
656 - (void)uploadObjectUsingHashMapFailed:(ASIPithosObjectRequest *)objectRequest {
657 NSLog(@"Upload using hashmap failed");
658 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
661 - (void)uploadMissingBlockFinished:(ASIPithosObjectRequest *)objectRequest {
662 NSLog(@"Upload of missing block completed: %@", [objectRequest url]);
663 if (objectRequest.responseStatusCode == 201) {
664 NSIndexSet *missingBlocks = [objectRequest.userInfo objectForKey:@"missingBlocks"];
665 NSUInteger missingBlockIndex = [[objectRequest.userInfo objectForKey:@"missingBlockIndex"] unsignedIntegerValue];
666 missingBlockIndex = [missingBlocks indexGreaterThanIndex:missingBlockIndex];
667 if (missingBlockIndex == NSNotFound) {
668 NSArray *hashes = [objectRequest.userInfo objectForKey:@"hashes"];
669 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities writeObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
670 objectName:[objectRequest.userInfo objectForKey:@"objectName"]
671 contentType:[objectRequest.userInfo objectForKey:@"contentType"]
672 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
673 blockHash:[objectRequest.userInfo objectForKey:@"blockHash"]
674 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
677 newObjectRequest.delegate = self;
678 newObjectRequest.didFinishSelector = @selector(uploadObjectUsingHashMapFinished:);
679 newObjectRequest.didFailSelector = @selector(uploadObjectUsingHashMapFailed:);
680 newObjectRequest.userInfo = objectRequest.userInfo;
681 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlocks"];
682 [(NSMutableDictionary *)(newObjectRequest.userInfo) removeObjectForKey:@"missingBlockIndex"];
683 [newObjectRequest startAsynchronous];
685 ASIPithosObjectRequest *newObjectRequest = [PithosFileUtilities updateObjectDataRequestWithContainerName:[objectRequest.userInfo objectForKey:@"containerName"]
686 objectName:@".upload"
687 blockSize:[[objectRequest.userInfo objectForKey:@"blockSize"] unsignedIntegerValue]
688 forFile:[objectRequest.userInfo objectForKey:@"filePath"]
689 missingBlockIndex:missingBlockIndex];
690 newObjectRequest.delegate = self;
691 newObjectRequest.didFinishSelector = @selector(uploadMissingBlockFinished:);
692 newObjectRequest.didFailSelector = @selector(uploadMissingBlockFailed:);
693 newObjectRequest.userInfo = objectRequest.userInfo;
694 [(NSMutableDictionary *)(newObjectRequest.userInfo) setObject:[NSNumber numberWithUnsignedInteger:missingBlockIndex] forKey:@"missingBlockIndex"];
695 [newObjectRequest startAsynchronous];
698 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
702 - (void)uploadMissingBlockFailed:(ASIPithosObjectRequest *)objectRequest {
703 NSLog(@"Upload of missing block failed");
704 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
708 #pragma mark NSSplitViewDelegate
710 - (CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex {
714 - (CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex {
718 - (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex {
719 if (proposedPosition < 120)
721 else if (proposedPosition > 220)
724 return proposedPosition;
728 #pragma mark NSOutlineViewDelegate
730 - (BOOL)outlineView:outlineView shouldSelectItem:(id)item {
731 return ([[item representedObject] isLeaf]);
734 - (BOOL)outlineView:(NSOutlineView *)outlineView isGroupItem:(id)item {
735 return (![[item representedObject] isLeaf]);
738 - (void)outlineViewSelectionDidChange:(NSNotification *)notification {
739 PithosNode *node = [[[outlineView itemAtRow:[outlineView selectedRow]] representedObject] representedObject];
742 [browser loadColumnZero];
748 #pragma mark NSMenuDelegate
750 - (void)menuNeedsUpdate:(NSMenu *)menu {
751 NSInteger column = [browser clickedColumn];
752 NSInteger row = [browser clickedRow];
753 [menu removeAllItems];
754 NSMenuItem *menuItem;
755 if ((column == -1) || (row == -1)) {
756 // General context menu
757 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
758 PithosNode *menuNode;
759 if ([menuNodesIndexPaths count] == 0) {
760 menuNode = [browser parentForItemsInColumn:0];
761 } else if (([menuNodesIndexPaths count] != 1) ||
762 ([[browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]] class] == [PithosObjectNode class])) {
763 menuNode = [browser parentForItemsInColumn:([[menuNodesIndexPaths objectAtIndex:0] length] - 1)];
765 menuNode = [browser itemAtIndexPath:[menuNodesIndexPaths objectAtIndex:0]];
767 NSArray *menuNodes = [NSArray arrayWithObject:menuNode];
769 menuItem = [[[NSMenuItem alloc] initWithTitle:@"New Folder" action:@selector(newFolder:) keyEquivalent:@""] autorelease];
770 [menuItem setRepresentedObject:menuNodes];
771 [menu addItem:menuItem];
772 [menu addItem:[NSMenuItem separatorItem]];
774 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
775 [menuItem setRepresentedObject:menuNodes];
776 [menu addItem:menuItem];
779 NSIndexPath *clickedNodeIndexPath = [[browser indexPathForColumn:column] indexPathByAddingIndex:row];
780 NSArray *menuNodesIndexPaths = [browser selectionIndexPaths];
781 NSMutableArray *menuNodes = [NSMutableArray arrayWithCapacity:[menuNodesIndexPaths count]];
782 if ([menuNodesIndexPaths containsObject:clickedNodeIndexPath]) {
783 for (NSIndexPath *nodeIndexPath in menuNodesIndexPaths) {
784 [menuNodes addObject:[browser itemAtIndexPath:nodeIndexPath]];
787 [menuNodes addObject:[browser itemAtIndexPath:clickedNodeIndexPath]];
789 // Move to Trash (pithos container only)
791 if ([rootNode class] == [PithosContainerNode class]) {
792 if ([rootNode.pithosContainer.name isEqualToString:@"pithos"]) {
793 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Move to Trash" action:@selector(moveToTrash:) keyEquivalent:@""] autorelease];
794 [menuItem setRepresentedObject:menuNodes];
795 [menu addItem:menuItem];
797 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Delete" action:@selector(deleteObject:) keyEquivalent:@""] autorelease];
798 [menuItem setRepresentedObject:menuNodes];
799 [menu addItem:menuItem];
800 [menu addItem:[NSMenuItem separatorItem]];
803 menuItem = [[[NSMenuItem alloc] initWithTitle:@"Get Info" action:@selector(getInfo:) keyEquivalent:@""] autorelease];
804 [menuItem setRepresentedObject:menuNodes];
805 [menu addItem:menuItem];
810 #pragma mark Menu Actions
812 - (void)newFolder:(NSMenuItem *)sender {
813 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
814 if ([node class] == [PithosContainerNode class]) {
815 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
816 dispatch_async(queue, ^{
817 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
818 subdirName:@"untitled folder"];
819 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
820 objectName:safeObjectName
822 contentType:@"application/directory"
824 contentDisposition:nil
827 isPublic:ASIPithosObjectRequestPublicIgnore
830 objectRequest.delegate = self;
831 objectRequest.didFinishSelector = @selector(newFolderFinished:);
832 objectRequest.didFailSelector = @selector(newFolderFailed:);
833 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
836 [objectRequest startAsynchronous];
838 } else if (([node class] == [PithosSubdirNode class]) &&
839 (node.pithosObject.subdir ||
840 ![node.pithosObject.name hasSuffix:@"/"])) {
841 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
842 dispatch_async(queue, ^{
843 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:node.pithosContainer.name
844 subdirName:[node.pithosObject.name stringByAppendingPathComponent:@"untitled folder"]];
845 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:node.pithosContainer.name
846 objectName:safeObjectName
848 contentType:@"application/directory"
850 contentDisposition:nil
853 isPublic:ASIPithosObjectRequestPublicIgnore
856 objectRequest.delegate = self;
857 objectRequest.didFinishSelector = @selector(newFolderFinished:);
858 objectRequest.didFailSelector = @selector(newFolderFailed:);
859 objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:
862 [objectRequest startAsynchronous];
868 - (void)getInfo:(NSMenuItem *)sender {
869 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
870 [node showPithosNodeInfo:sender];
874 - (void)deleteObject:(NSMenuItem *)sender {
875 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
876 if (([node class] == [PithosObjectNode class]) ||
877 (([node class] == [PithosSubdirNode class]) &&
878 !node.pithosObject.subdir &&
879 [node.pithosObject.name hasSuffix:@"/"])) {
880 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithContainerName:node.pithosContainer.name
881 objectName:node.pithosObject.name];
882 objectRequest.delegate = self;
883 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
884 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
885 [objectRequest startAsynchronous];
886 } else if ([node class] == [PithosSubdirNode class]) {
887 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
888 dispatch_async(queue, ^{
889 NSArray *objectRequests = [PithosFileUtilities deleteObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
890 objectName:node.pithosObject.name];
891 if (objectRequests) {
892 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
893 objectRequest.delegate = self;
894 objectRequest.didFinishSelector = @selector(deleteObjectFinished:);
895 objectRequest.didFailSelector = @selector(deleteObjectFailed:);
896 [objectRequest startAsynchronous];
904 - (void)moveToTrash:(NSMenuItem *)sender {
905 for (PithosNode *node in ((NSArray *)[sender representedObject])) {
906 if (([node class] == [PithosObjectNode class]) ||
907 (([node class] == [PithosSubdirNode class]) &&
908 !node.pithosObject.subdir &&
909 [node.pithosObject.name hasSuffix:@"/"])) {
910 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
911 dispatch_async(queue, ^{
912 NSString *safeObjectName = [PithosFileUtilities safeObjectNameForContainerName:@"trash"
913 objectName:node.pithosObject.name];
914 if (safeObjectName) {
915 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithContainerName:node.pithosContainer.name
916 objectName:node.pithosObject.name
919 contentDisposition:nil
922 isPublic:ASIPithosObjectRequestPublicIgnore
924 destinationContainerName:@"trash"
925 destinationObjectName:safeObjectName];
926 objectRequest.delegate = self;
927 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
928 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
929 [objectRequest startAsynchronous];
932 } else if ([node class] == [PithosSubdirNode class]) {
933 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
934 dispatch_async(queue, ^{
935 NSString *safeObjectName = [PithosFileUtilities safeSubdirNameForContainerName:@"trash"
936 subdirName:node.pithosObject.name];
937 if (safeObjectName) {
938 NSArray *objectRequests = [PithosFileUtilities moveObjectRequestsForSubdirWithContainerName:node.pithosContainer.name
939 objectName:node.pithosObject.name
940 destinationContainerName:@"trash"
941 destinationObjectName:safeObjectName];
942 if (objectRequests) {
943 for (ASIPithosObjectRequest *objectRequest in objectRequests) {
944 objectRequest.delegate = self;
945 objectRequest.didFinishSelector = @selector(moveToTrashFinished:);
946 objectRequest.didFailSelector = @selector(moveToTrashFailed:);
947 [objectRequest startAsynchronous];
957 #pragma mark Menu Actions ASIHTTPRequestDelegate
959 - (void)newFolderFinished:(ASIPithosObjectRequest *)objectRequest {
960 if (objectRequest.responseStatusCode == 201) {
961 NSLog(@"New application/directory object created: %@", [objectRequest url]);
962 PithosNode *node = [objectRequest.userInfo objectForKey:@"node"];
963 [node invalidateChildren];
966 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
970 - (void)newFolderFailed:(ASIPithosObjectRequest *)objectRequest {
971 NSLog(@"Creation of new application/directory object failed");
972 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
975 - (void)deleteObjectFinished:(ASIPithosObjectRequest *)objectRequest {
976 if (objectRequest.responseStatusCode == 204) {
977 NSLog(@"Object deleted: %@", [objectRequest url]);
980 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
984 - (void)deleteObjectFailed:(ASIPithosObjectRequest *)objectRequest {
985 NSLog(@"Delete of object failed");
986 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];
989 - (void)moveToTrashFinished:(ASIPithosObjectRequest *)objectRequest {
990 if (objectRequest.responseStatusCode == 201) {
991 NSLog(@"Object moved: %@", [objectRequest url]);
994 [PithosFileUtilities unexpectedResponseStatusAlertWithRequest:objectRequest];
998 - (void)moveToTrashFailed:(ASIPithosObjectRequest *)objectRequest {
999 NSLog(@"Move of object failed");
1000 [PithosFileUtilities httpRequestErrorAlertWithRequest:objectRequest];