2 // PithosFileUtilities.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 "PithosFileUtilities.h"
39 #import "ASIPithosContainerRequest.h"
40 #import "ASIPithosObjectRequest.h"
41 #import "ASIPithosObject.h"
42 #import "HashMapHash.h"
44 @implementation PithosFileUtilities
49 + (ASIPithosObjectRequest *)objectDataRequestWithContainerName:(NSString *)containerName
50 objectName:(NSString *)objectName
51 toDirectory:(NSString *)directoryPath
52 checkIfExists:(BOOL)ifExists {
53 NSString *fileName = [objectName lastPathComponent];
54 if([objectName hasSuffix:@"/"])
55 fileName = [fileName stringByAppendingString:@"/"];
56 fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"];
58 NSFileManager *defaultManager = [NSFileManager defaultManager];
60 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName];
61 if (ifExists && [defaultManager fileExistsAtPath:destinationPath]) {
62 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
63 [alert setMessageText:@"File Exists"];
64 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]];
65 [alert addButtonWithTitle:@"OK"];
66 [alert addButtonWithTitle:@"Cancel"];
67 NSInteger choice = [alert runModal];
68 if (choice == NSAlertSecondButtonReturn)
72 BOOL directoryIsDirectory;
73 BOOL directoryExists = [defaultManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory];
75 if (!directoryExists) {
76 [defaultManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error];
77 } else if (!directoryIsDirectory) {
78 [defaultManager removeItemAtPath:directoryPath error:&error];
81 NSLog(@"Cannot remove existing file '%@': %@", fileName, error);
82 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
83 [alert setMessageText:@"Removal Error"];
84 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", fileName, error]];
85 [alert addButtonWithTitle:@"OK"];
90 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithContainerName:containerName
91 objectName:objectName];
92 objectRequest.downloadDestinationPath = destinationPath;
93 objectRequest.userInfo = [NSDictionary dictionaryWithObjectsAndKeys:
94 fileName, @"fileName",
95 destinationPath, @"filePath",
100 + (NSArray *)objectDataRequestsForSubdirWithContainerName:(NSString *)containerName
101 objectName:(NSString *)objectName
102 toDirectory:(NSString *)directoryPath
103 checkIfExists:(BOOL)ifExists {
104 NSString *subdirName = [objectName lastPathComponent];
105 NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName];
106 if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) {
107 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
108 [alert setMessageText:@"File exists"];
109 [alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]];
110 [alert addButtonWithTitle:@"OK"];
111 [alert addButtonWithTitle:@"Cancel"];
112 NSInteger choice = [alert runModal];
113 if (choice == NSAlertSecondButtonReturn)
117 NSMutableArray *objects = [NSMutableArray array];
118 NSString *marker = nil;
120 ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithContainerName:containerName
129 [containerRequest startSynchronous];
130 if ([containerRequest error]) {
131 NSLog(@"HTTP Request Error: %@\nResponse String: %@", [containerRequest error], [containerRequest responseString]);
132 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
133 [alert setMessageText:@"HTTP Request Error"];
134 [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@\nResponse String: %@",
135 [containerRequest error], [containerRequest responseString]]];
136 [alert addButtonWithTitle:@"OK"];
140 NSArray *someObjects = [containerRequest objects];
141 [objects addObjectsFromArray:someObjects];
142 if ([someObjects count] < 10000)
145 marker = [[someObjects lastObject] name];
148 NSFileManager *defaultManager = [NSFileManager defaultManager];
149 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]];
150 NSUInteger subdirPrefixLength = [objectName length];
151 for (ASIPithosObject *object in objects) {
152 if (object.subdir || [object.contentType isEqualToString:@"application/directory"]) {
153 NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
154 subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]];
156 BOOL directoryIsDirectory;
157 BOOL directoryExists = [defaultManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory];
158 NSError *error = nil;
159 if (!directoryExists) {
160 [defaultManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error];
162 NSLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error);
163 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
164 [alert setMessageText:@"Create Directory Error"];
165 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@",
166 subdirDirectoryPath, error]];
167 [alert addButtonWithTitle:@"OK"];
170 } else if (!directoryIsDirectory) {
171 [defaultManager removeItemAtPath:subdirDirectoryPath error:&error];
173 NSLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error);
174 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
175 [alert setMessageText:@"Remove File Error"];
176 [alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@",
177 subdirDirectoryPath, error]];
178 [alert addButtonWithTitle:@"OK"];
183 NSString *fileName = [object.name lastPathComponent];
184 if([object.name hasSuffix:@"/"])
185 fileName = [fileName stringByAppendingString:@"/"];
187 NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName];
188 objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]];
190 [objectRequests addObject:[self objectDataRequestWithContainerName:containerName
191 objectName:object.name
192 toDirectory:objectDirectoryPath
197 return objectRequests;
203 + (ASIPithosObjectRequest *)writeObjectDataRequestWithContainerName:(NSString *)containerName
204 objectName:(NSString *)objectName
205 contentType:(NSString *)contentType
206 blockSize:(NSUInteger)blockSize
207 blockHash:(NSString *)blockHash
208 forFile:(NSString *)filePath
209 checkIfExists:(BOOL)ifExists
210 hashes:(NSArray **)hashes {
212 NSString *responseString;
215 BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName
216 responseString:&responseString error:&error isDirectory:&isDirectory];
219 alert = [[[NSAlert alloc] init] autorelease];
220 [alert setMessageText:@"HTTP Request Error"];
221 [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@", error]];
222 [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@\nResponse String: %@",
223 error, responseString]];
224 [alert addButtonWithTitle:@"OK"];
227 } else if (objectExists) {
228 alert = [[[NSAlert alloc] init] autorelease];
230 [alert setMessageText:@"Directory Exists"];
231 [alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
233 [alert setMessageText:@"Object Exists"];
234 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
236 [alert addButtonWithTitle:@"OK"];
237 [alert addButtonWithTitle:@"Cancel"];
238 NSInteger choice = [alert runModal];
239 if (choice == NSAlertSecondButtonReturn)
245 *hashes = [HashMapHash calculateObjectHashMap:filePath withBlockHash:blockHash andBlockSize:blockSize];
248 NSUInteger bytes = [self bytesOfFile:filePath];
249 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
250 objectName:objectName
251 contentType:contentType
253 contentDisposition:nil
256 isPublic:ASIPithosObjectRequestPublicIgnore
262 return objectRequest;
265 + (ASIPithosObjectRequest *)updateObjectDataRequestWithContainerName:(NSString *)containerName
266 objectName:(NSString *)objectName
267 contentType:(NSString *)contentType
268 blockSize:(NSUInteger)blockSize
269 forFile:(NSString *)filePath
270 hashes:(NSArray *)hashes
271 missingHashesResponse:(NSString *)missingHashesResponse {
272 NSArray *responseLines = [missingHashesResponse componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
273 NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet];
274 for (NSString *line in responseLines) {
277 NSUInteger missingBlock = [hashes indexOfObject:line];
278 if (missingBlock != -1)
279 [missingBlocks addIndex:missingBlock];
282 NSFileManager *defaultManager = [NSFileManager defaultManager];
283 NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath];
285 // http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html
286 NSString *tempFileTemplate = NSTemporaryDirectory();
287 if (tempFileTemplate == nil)
288 tempFileTemplate = @"/tmp";
289 tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"];
290 const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation];
291 char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1);
292 strcpy(tempFileNameCString, tempFileTemplateCString);
293 int fileDescriptor = mkstemp(tempFileNameCString);
294 NSString *tempFilePath = [defaultManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)];
295 if (fileDescriptor == -1) {
296 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
297 [alert setMessageText:@"Create Temporary File Error"];
298 [alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]];
299 [alert addButtonWithTitle:@"OK"];
303 free(tempFileNameCString);
304 NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES];
306 [missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){
307 [fileHandle seekToFileOffset:(idx*blockSize)];
308 [tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]];
310 [tempFileHandle closeFile];
312 return [ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
313 objectName:objectName
315 contentType:contentType
317 contentDisposition:nil
320 isPublic:ASIPithosObjectRequestPublicIgnore
325 + (NSArray *)writeObjectDataRequestsWithContainerName:(NSString *)containerName
326 objectName:(NSString *)objectName
327 blockSize:(NSUInteger)blockSize
328 blockHash:(NSString *)blockHash
329 forDirectory:(NSString *)directoryPath
330 checkIfExists:(BOOL)ifExists
331 objectNames:(NSMutableArray **)objectNames
332 contentTypes:(NSMutableArray **)contentTypes
333 filePaths:(NSMutableArray **)filePaths
334 hashesArrays:(NSMutableArray **)hashesArrays {
336 NSString *responseString;
339 BOOL objectExists = [self objectExistsAtContainerName:containerName objectName:objectName
340 responseString:&responseString error:&error isDirectory:&isDirectory];
343 alert = [[[NSAlert alloc] init] autorelease];
344 [alert setMessageText:@"HTTP Request Error"];
345 [alert setInformativeText:[NSString stringWithFormat:@"HTTP Request Error: %@\nResponse String: %@",
346 error, responseString]];
347 [alert addButtonWithTitle:@"OK"];
350 } else if (objectExists && !isDirectory) {
351 alert = [[[NSAlert alloc] init] autorelease];
352 [alert setMessageText:@"Object Exists"];
353 [alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]];
354 [alert addButtonWithTitle:@"OK"];
355 [alert addButtonWithTitle:@"Cancel"];
356 NSInteger choice = [alert runModal];
357 if (choice == NSAlertSecondButtonReturn)
362 NSFileManager *defaultManager = [NSFileManager defaultManager];
363 NSError *error = nil;
364 NSArray *subPaths = [defaultManager subpathsOfDirectoryAtPath:directoryPath error:&error];
366 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
367 [alert setMessageText:@"Directory Read Error"];
368 [alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@",
369 [directoryPath lastPathComponent], error]];
370 [alert addButtonWithTitle:@"OK"];
374 NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]];
375 *objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]];
376 *contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]];
377 *filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]];
378 *hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]];
380 NSString *subObjectName;
383 NSString *contentType;
385 for (NSString *objectNameSuffix in subPaths) {
386 filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix];
387 if ([defaultManager fileExistsAtPath:filePath isDirectory:&isDirectory]) {
389 hashes = [HashMapHash calculateObjectHashMap:filePath withBlockHash:blockHash andBlockSize:blockSize];
391 subObjectName = [objectName stringByAppendingPathComponent:objectNameSuffix];
392 bytes = [self bytesOfFile:filePath];
394 contentType = [self contentTypeOfFile:filePath error:&error];
395 if (contentType == nil)
396 contentType = @"application/binary";
398 NSLog(@"contentType detection error: %@", error);
399 [objectRequests addObject:[ASIPithosObjectRequest writeObjectDataRequestWithContainerName:containerName
400 objectName:subObjectName
401 contentType:contentType
403 contentDisposition:nil
406 isPublic:ASIPithosObjectRequestPublicIgnore
412 [*objectNames addObject:subObjectName];
413 [*contentTypes addObject:contentType];
414 [*filePaths addObject:filePath];
415 [*hashesArrays addObject:hashes];
422 return objectRequests;
426 #pragma mark Helper Methods
428 + (NSUInteger)bytesOfFile:(NSString *)filePath {
429 NSFileManager *defaultManager = [NSFileManager defaultManager];
430 NSDictionary *attributes = [defaultManager attributesOfItemAtPath:filePath error:nil];
431 return [[attributes objectForKey:NSFileSize] intValue];
434 + (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error {
435 NSURLResponse *response = nil;
436 [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:filePath]
437 cachePolicy:NSURLCacheStorageNotAllowed
439 returningResponse:&response
441 return [response MIMEType];
444 + (BOOL)objectExistsAtContainerName:(NSString *)containerName objectName:(NSString *)objectName
445 responseString:(NSString **)responseString error:(NSError **)error isDirectory:(BOOL *)isDirectory {
446 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithContainerName:containerName
447 objectName:objectName];
448 [objectRequest startSynchronous];
449 *responseString = [objectRequest responseString];
450 *error = [objectRequest error];
453 } else if (objectRequest.responseStatusCode == 200) {
454 *isDirectory = [[objectRequest contentType] isEqualToString:@"application/directory"];
460 + (int)deleteObjectAtContainerName:(NSString *)containerName objectName:(NSString *)objectName
461 responseStatusMessage:(NSString **)responseStatusMessage error:(NSError **)error {
462 ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest deleteContainerRequestWithContainerName:containerName
463 objectName:objectName];
464 [objectRequest startSynchronous];
465 *error = [objectRequest error];
466 *responseStatusMessage = [NSString stringWithString:objectRequest.responseStatusMessage];
467 return objectRequest.responseStatusCode;
473 + (NSInteger)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request {
474 NSString *message = [NSString stringWithFormat:
475 @"HTTP request error: %@\n%@ URL: %@\nResponse String: %@",
477 request.requestMethod,
479 [request responseString]];
480 NSLog(@"%@", message);
481 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
482 [alert setMessageText:@"HTTP Request Error"];
483 [alert setInformativeText:message];
484 [alert addButtonWithTitle:@"OK"];
485 return [alert runModal];
488 + (NSInteger)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request {
489 NSString *message = [NSString stringWithFormat:
490 @"Unexpected response status %d: %@\n%@ URL: %@\nResponse String: %@",
491 request.responseStatusCode,
492 request.responseStatusMessage,
493 request.requestMethod,
495 [request responseString]];
496 NSLog(@"%@", message);
497 NSAlert *alert = [[[NSAlert alloc] init] autorelease];
498 [alert setMessageText:@"Unexpected Response Status"];
499 [alert setInformativeText:message];
500 [alert addButtonWithTitle:@"OK"];
501 return [alert runModal];