Initial implementation of drag and drop directory upload.
[pithos-macos] / pithos-macos / PithosFileUtilities.m
index dd55d03..ae53d4e 100644 (file)
@@ -60,7 +60,7 @@
     NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName];
     if (ifExists && [defaultManager fileExistsAtPath:destinationPath]) {
         NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-        [alert setMessageText:@"File exists"];
+        [alert setMessageText:@"File Exists"];
         [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
         [alert addButtonWithTitle:@"OK"];
         [alert addButtonWithTitle:@"Cancel"];
         [defaultManager removeItemAtPath:directoryPath error:&error];
     }
     if (error) {
-        NSLog(@"error: %@", error);
-        // XXX do something on error, an alert maybe
+        NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
+        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+        [alert setMessageText:@"Removal Error"];
+        [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, error]];
+        [alert addButtonWithTitle:@"OK"];
+        [alert runModal];
         return nil;
     }
 
                                                                                                                until:nil];
         [containerRequest startSynchronous];
         if ([containerRequest error]) {
-            NSLog(@"error:%@", [containerRequest error]);
-            // XXX do something on error
+            NSLog(@"HTTP Request Error: %@\nResponse String: %@", [containerRequest error], [containerRequest responseString]);
+            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+            [alert setMessageText:@"HTTP Request Error"];
+            [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@\nResponse String: %@", 
+                                       [containerRequest error], [containerRequest responseString]]];
+            [alert addButtonWithTitle:@"OK"];
+            [alert runModal];        
             return nil;
         }
         NSArray *someObjects = [containerRequest objects];
             NSError *error = nil;
             if (!directoryExists) {
                 [defaultManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
+                if (error) {
+                    NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
+                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+                    [alert setMessageText:@"Create Directory Error"];
+                    [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", 
+                                               subdirDirectoryPath, error]];
+                    [alert addButtonWithTitle:@"OK"];
+                    [alert runModal];
+                }
             } else if (!directoryIsDirectory) {
-                [defaultManager removeItemAtPath:directoryPath error:&error];
-            }
-            if (error) {
-                NSLog(@"error: %@", error);
-                // XXX do something on error, an alert maybe
+                [defaultManager removeItemAtPath:subdirDirectoryPath error:&error];
+                if (error) {
+                    NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
+                    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+                    [alert setMessageText:@"Remove File Error"];
+                    [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", 
+                                               subdirDirectoryPath, error]];
+                    [alert addButtonWithTitle:@"OK"];
+                    [alert runModal];
+                }
             }
         } else {
             NSString *fileName = [object.name lastPathComponent];
                                                       checkIfExists:(BOOL)ifExists 
                                                              hashes:(NSArray **)hashes {
     if (ifExists) {
+        NSString *responseString;
         NSError *error;
-        BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName error:&error];
+        BOOL isDirectory;
+        BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName 
+                                               responseString:&responseString error:&error isDirectory:&isDirectory];
         NSAlert *alert;
         if (error) {
             alert = [[[NSAlert alloc] init] autorelease];
             [alert setMessageText:@"HTTP Request Error"];
-            [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", error]];
+            [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@", error]];
+            [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@\nResponse String: %@", 
+                                       error, responseString]];
             [alert addButtonWithTitle:@"OK"];
             [alert runModal];
             return nil;
         } else if (objectExists) {
             alert = [[[NSAlert alloc] init] autorelease];
-            [alert setMessageText:@"Object exists"];
-            [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
+            if (isDirectory) {
+                [alert setMessageText:@"Directory Exists"];
+                [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
+            } else {
+                [alert setMessageText:@"Object Exists"];
+                [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
+            }
             [alert addButtonWithTitle:@"OK"];
             [alert addButtonWithTitle:@"Cancel"];
             NSInteger choice = [alert runModal];
             if (choice == NSAlertSecondButtonReturn)
                 return nil;
-            NSString *responseStatusMessage;
-            int responseStatusCode = [self deleteObjectAtContainerName:containerName objectName:objectName 
-                                                 responseStatusMessage:&responseStatusMessage error:&error];
-            if (error) {
-                alert = [[[NSAlert alloc] init] autorelease];
-                [alert setMessageText:@"HTTP Request Error"];
-                [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", error]];
-                [alert addButtonWithTitle:@"OK"];
-                [alert runModal];
-                return nil;
-            } else if (responseStatusCode != 204) {
-                alert = [[[NSAlert alloc] init] autorelease];
-                [alert setMessageText:@"Unexpected Response Status"];
-                [alert setInformativeText:[NSString stringWithFormat:@"Unexpected response status %d - %@", responseStatusCode, responseStatusMessage]];
-                [alert addButtonWithTitle:@"OK"];
-                [alert runModal];
-                return nil;
-            }
         }
     }
     
                                                            blockSize:(NSUInteger)blockSize 
                                                              forFile:(NSString *)filePath 
                                                               hashes:(NSArray *)hashes 
-                                               missingHashesResponse:(NSString *)missingHashesResponse
-                                                       checkIfExists:(BOOL)ifExists {
-    BOOL objectExists = YES;
-    if (ifExists) {
-        NSError *error;
-        objectExists = [self objectExistsAtContainerName:containerName objectName:objectName error:&error];
-        if (error) {
-            NSAlert *alert = [[[NSAlert alloc] init] autorelease];
-            [alert setMessageText:@"HTTP Request Error"];
-            [alert setInformativeText:[NSString stringWithFormat:@"An error occured: %@", error]];
-            [alert addButtonWithTitle:@"OK"];
-            [alert runModal];
-            return nil;
-        }
-    }
-
+                                               missingHashesResponse:(NSString *)missingHashesResponse {
     NSArray *responseLines = [missingHashesResponse componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
     NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
     for (NSString *line in responseLines) {
     char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
     strcpy(tempFileNameCString, tempFileTemplateCString);
     int fileDescriptor = mkstemp(tempFileNameCString);
+    NSString *tempFilePath = [defaultManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
     if (fileDescriptor == -1) {
-        NSLog(@"temp file creation failed");
-        // XXX maybe alert?
+        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+        [alert setMessageText:@"Create Temporary File Error"];
+        [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
+        [alert addButtonWithTitle:@"OK"];
+        [alert runModal];
         return nil;
     }
-    NSString *tempFilePath = [defaultManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
     free(tempFileNameCString);
     NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
 
     }];
     [tempFileHandle closeFile];
 
-    if (objectExists) {
-        return [ASIPithosObjectRequest updateObjectDataRequestWithContainerName:containerName 
-                                                                     objectName:objectName
-                                                                contentEncoding:nil
-                                                             contentDisposition:nil 
-                                                                       manifest:nil 
-                                                                        sharing:nil 
-                                                                       isPublic:ASIPithosObjectRequestPublicIgnore 
-                                                                       metadata:nil 
-                                                                         update:NO 
-                                                                   contentRange:@"bytes */*"
-                                                                          bytes:[NSNumber numberWithUnsignedInteger:0]
-                                                                           file:tempFilePath];
-    } else {
-        return [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
-                                                                    objectName:objectName 
-                                                                          eTag:nil
-                                                                   contentType:contentType 
-                                                               contentEncoding:nil 
-                                                            contentDisposition:nil 
-                                                                      manifest:nil 
-                                                                       sharing:nil 
-                                                                      isPublic:ASIPithosObjectRequestPublicIgnore 
-                                                                      metadata:nil 
-                                                                          file:tempFilePath];
+    return [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
+                                                                objectName:objectName 
+                                                                      eTag:nil
+                                                               contentType:contentType 
+                                                           contentEncoding:nil 
+                                                        contentDisposition:nil 
+                                                                  manifest:nil 
+                                                                   sharing:nil 
+                                                                  isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                  metadata:nil 
+                                                                      file:tempFilePath];
+}
+
++ (NSArray *)writeObjectDataRequestsWithContainerName:(NSString *)containerName
+                                           objectName:(NSString *)objectName
+                                            blockSize:(NSUInteger)blockSize 
+                                            blockHash:(NSString *)blockHash 
+                                         forDirectory:(NSString *)directoryPath 
+                                        checkIfExists:(BOOL)ifExists 
+                                          objectNames:(NSMutableArray **)objectNames
+                                         contentTypes:(NSMutableArray **)contentTypes
+                                            filePaths:(NSMutableArray **)filePaths 
+                                         hashesArrays:(NSMutableArray **)hashesArrays {
+    if (ifExists) {
+        NSString *responseString;
+        NSError *error;
+        BOOL isDirectory;
+        BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName 
+                                               responseString:&responseString error:&error isDirectory:&isDirectory];
+        NSAlert *alert;
+        if (error) {
+            alert = [[[NSAlert alloc] init] autorelease];
+            [alert setMessageText:@"HTTP Request Error"];
+            [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@\nResponse String: %@", 
+                                       error, responseString]];
+            [alert addButtonWithTitle:@"OK"];
+            [alert runModal];
+            return nil;
+        } else if (objectExists && !isDirectory) {
+            alert = [[[NSAlert alloc] init] autorelease];
+            [alert setMessageText:@"Object Exists"];
+            [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
+            [alert addButtonWithTitle:@"OK"];
+            [alert addButtonWithTitle:@"Cancel"];
+            NSInteger choice = [alert runModal];
+            if (choice == NSAlertSecondButtonReturn)
+                return nil;
+        }
+    }
+
+    NSFileManager *defaultManager = [NSFileManager defaultManager];
+    NSError *error = nil;
+    NSArray *subPaths = [defaultManager subpathsOfDirectoryAtPath:directoryPath error:&error];
+    if (error) {
+        NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+        [alert setMessageText:@"Directory Read Error"];
+        [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", 
+                                   [directoryPath lastPathComponent], error]];
+        [alert addButtonWithTitle:@"OK"];
+        [alert runModal];
+        return nil;
     }
+    NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
+    *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
+    *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
+    *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
+    *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
+    BOOL isDirectory;
+    NSString *subObjectName;
+    NSArray *hashes;
+    NSUInteger bytes;
+    NSString *contentType;
+    NSString *filePath;
+    for (NSString *objectNameSuffix in subPaths) {
+        filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
+        if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
+            if (!isDirectory) {
+                hashes = [HashMapHash calculateObjectHashMap:filePath withBlockHash:blockHash andBlockSize:blockSize];
+                if (hashes) {
+                    subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix];
+                    bytes = [self bytesOfFile:filePath];
+                    error = nil;
+                    contentType = [self contentTypeOfFile:filePath error:&error];
+                    if (contentType == nil)
+                        contentType = @"application/binary";
+                    if (error)
+                        NSLog(@"contentType detection error: %@", error);
+                    [objectRequests addObject:[ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName 
+                                                                                                   objectName:subObjectName 
+                                                                                                  contentType:contentType 
+                                                                                              contentEncoding:nil 
+                                                                                           contentDisposition:nil 
+                                                                                                     manifest:nil 
+                                                                                                      sharing:nil 
+                                                                                                     isPublic:ASIPithosObjectRequestPublicIgnore 
+                                                                                                     metadata:nil
+                                                                                                    blockSize:blockSize
+                                                                                                    blockHash:blockHash 
+                                                                                                       hashes:hashes 
+                                                                                                        bytes:bytes]];
+                    [*objectNames addObject:subObjectName];
+                    [*contentTypes addObject:contentType];
+                    [*filePaths addObject:filePath];
+                    [*hashesArrays addObject:hashes];
+                }
+                
+            }//XXX else
+        }
+    }
+    
+    return objectRequests;
 }
 
 #pragma mark -
     return [[attributes objectForKey:NSFileSize] intValue];
 }
 
-+ (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName error:(NSError **)error {
++ (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
+    NSURLResponse *response = nil;
+    [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath] 
+                                                             cachePolicy:NSURLCacheStorageNotAllowed 
+                                                         timeoutInterval:.1] 
+                          returningResponse:&response 
+                                      error:error];
+    return [response MIMEType];
+}
+
++ (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName 
+                     responseString:(NSString **)responseString error:(NSError **)error isDirectory:(BOOL *)isDirectory {
     ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithContainerName:containerName 
                                                                                                 objectName:objectName];
     [objectRequest startSynchronous];
+    *responseString = [objectRequest responseString];
     *error = [objectRequest error];
     if (*error) {
         return NO;
     } else if (objectRequest.responseStatusCode == 200) {
+        *isDirectory = [[objectRequest contentType] isEqualToString:@"application/directory"];
         return YES;
     }
     return NO;
     return objectRequest.responseStatusCode;
 }
 
+#pragma mark -
+#pragma mark Alerts
+
++ (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
+    NSString *message = [NSString stringWithFormat:
+                         @"HTTP request error: %@\n%@ URL: %@\nResponse String: %@", 
+                         [request error], 
+                         request.requestMethod, 
+                         request.url, 
+                         [request responseString]];
+    NSLog(@"%@", message);
+    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+    [alert setMessageText:@"HTTP Request Error"];
+    [alert setInformativeText:message];
+    [alert addButtonWithTitle:@"OK"];
+    return [alert runModal];
+}
+
++ (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
+    NSString *message = [NSString stringWithFormat:
+                         @"Unexpected response status %d: %@\n%@ URL: %@\nResponse String: %@", 
+                         request.responseStatusCode, 
+                         request.responseStatusMessage, 
+                         request.requestMethod, 
+                         request.url, 
+                         [request responseString]];
+    NSLog(@"%@", message);
+    NSAlert *alert = [[[NSAlert alloc] init] autorelease];
+    [alert setMessageText:@"Unexpected Response Status"];
+    [alert setInformativeText:message];
+    [alert addButtonWithTitle:@"OK"];
+    return [alert runModal];
+}
+
 @end