root / pithos-macos / PithosUtilities.m @ 6d9d5dce
History | View | Annotate | Download (78.9 kB)
1 |
// |
---|---|
2 |
// PithosUtilities.m |
3 |
// pithos-macos |
4 |
// |
5 |
// Copyright 2011-2012 GRNET S.A. All rights reserved. |
6 |
// |
7 |
// Redistribution and use in source and binary forms, with or |
8 |
// without modification, are permitted provided that the following |
9 |
// conditions are met: |
10 |
// |
11 |
// 1. Redistributions of source code must retain the above |
12 |
// copyright notice, this list of conditions and the following |
13 |
// disclaimer. |
14 |
// |
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. |
19 |
// |
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. |
32 |
// |
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. |
37 |
|
38 |
#import "PithosUtilities.h" |
39 |
#import "ASINetworkQueue.h" |
40 |
#import "ASIPithos.h" |
41 |
#import "ASIPithosContainerRequest.h" |
42 |
#import "ASIPithosObjectRequest.h" |
43 |
#import "ASIPithosObject.h" |
44 |
#import "PithosAccount.h" |
45 |
#import "HashMapHash.h" |
46 |
|
47 |
@implementation PithosUtilities |
48 |
|
49 |
#pragma mark - |
50 |
#pragma mark Download |
51 |
|
52 |
+ (ASIPithosObjectRequest *)objectDataRequestWithPithos:(ASIPithos *)pithos |
53 |
containerName:(NSString *)containerName |
54 |
objectName:(NSString *)objectName |
55 |
version:(NSString *)version |
56 |
toDirectory:(NSString *)directoryPath |
57 |
withNewFileName:(NSString *)newFileName |
58 |
checkIfExists:(BOOL)ifExists |
59 |
sharingAccount:(NSString *)sharingAccount { |
60 |
NSString *fileName; |
61 |
if (newFileName) { |
62 |
fileName = [NSString stringWithString:newFileName]; |
63 |
} else { |
64 |
fileName = [objectName lastPathComponent]; |
65 |
if ([objectName hasSuffix:@"/"]) |
66 |
fileName = [fileName stringByAppendingString:@"/"]; |
67 |
} |
68 |
fileName = [fileName stringByReplacingOccurrencesOfString:@"/" withString:@":"]; |
69 |
|
70 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
71 |
|
72 |
NSString *destinationPath = [directoryPath stringByAppendingPathComponent:fileName]; |
73 |
if (ifExists && [fileManager fileExistsAtPath:destinationPath]) { |
74 |
__block NSInteger choice; |
75 |
dispatch_sync(dispatch_get_main_queue(), ^{ |
76 |
NSAlert *alert = [[NSAlert alloc] init]; |
77 |
[alert setMessageText:@"File Exists"]; |
78 |
[alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", fileName]]; |
79 |
[alert addButtonWithTitle:@"OK"]; |
80 |
[alert addButtonWithTitle:@"Cancel"]; |
81 |
choice = [alert runModal]; |
82 |
}); |
83 |
if (choice == NSAlertSecondButtonReturn) |
84 |
return nil; |
85 |
} |
86 |
|
87 |
BOOL directoryIsDirectory; |
88 |
BOOL directoryExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&directoryIsDirectory]; |
89 |
NSError *error = nil; |
90 |
if (!directoryExists) { |
91 |
[fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:&error]; |
92 |
} else if (!directoryIsDirectory) { |
93 |
[fileManager removeItemAtPath:directoryPath error:&error]; |
94 |
} |
95 |
if (error) { |
96 |
DLog(@"Cannot remove existing file '%@': %@", fileName, error); |
97 |
dispatch_async(dispatch_get_main_queue(), ^{ |
98 |
NSAlert *alert = [[NSAlert alloc] init]; |
99 |
[alert setMessageText:@"Removal Error"]; |
100 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file '%@': %@", |
101 |
fileName, [error localizedDescription]]]; |
102 |
[alert addButtonWithTitle:@"OK"]; |
103 |
[alert runModal]; |
104 |
}); |
105 |
return nil; |
106 |
} |
107 |
|
108 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos |
109 |
containerName:containerName |
110 |
objectName:objectName |
111 |
version:version]; |
112 |
if (sharingAccount) |
113 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
114 |
objectRequest.downloadDestinationPath = destinationPath; |
115 |
objectRequest.allowResumeForFileDownloads = YES; |
116 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
117 |
fileName, @"fileName", |
118 |
destinationPath, @"filePath", |
119 |
nil]; |
120 |
return objectRequest; |
121 |
} |
122 |
|
123 |
+ (NSArray *)objectDataRequestsForSubdirWithPithos:(ASIPithos *)pithos |
124 |
containerName:(NSString *)containerName |
125 |
objectName:(NSString *)objectName |
126 |
toDirectory:(NSString *)directoryPath |
127 |
checkIfExists:(BOOL)ifExists |
128 |
sharingAccount:(NSString *)sharingAccount { |
129 |
NSString *subdirName = [objectName lastPathComponent]; |
130 |
NSString *destinationPath = [directoryPath stringByAppendingPathComponent:subdirName]; |
131 |
if (ifExists && [[NSFileManager defaultManager] fileExistsAtPath:destinationPath]) { |
132 |
__block NSInteger choice; |
133 |
dispatch_sync(dispatch_get_main_queue(), ^{ |
134 |
NSAlert *alert = [[NSAlert alloc] init]; |
135 |
[alert setMessageText:@"File exists"]; |
136 |
[alert setInformativeText:[NSString stringWithFormat:@"A file or directory named '%@' already exists, do you want to replace it?", subdirName]]; |
137 |
[alert addButtonWithTitle:@"OK"]; |
138 |
[alert addButtonWithTitle:@"Cancel"]; |
139 |
choice = [alert runModal]; |
140 |
}); |
141 |
if (choice == NSAlertSecondButtonReturn) |
142 |
return nil; |
143 |
} |
144 |
|
145 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName |
146 |
delimiter:nil sharingAccount:sharingAccount]; |
147 |
if (objects == nil) |
148 |
return nil; |
149 |
|
150 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
151 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[objects count]]; |
152 |
NSUInteger subdirPrefixLength = [objectName length]; |
153 |
|
154 |
NSError *error = nil; |
155 |
[fileManager createDirectoryAtPath:[directoryPath stringByAppendingPathComponent:subdirName] withIntermediateDirectories:YES attributes:nil error:&error]; |
156 |
if (error) { |
157 |
DLog(@"Cannot create directory at '%@': %@", directoryPath, error); |
158 |
dispatch_async(dispatch_get_main_queue(), ^{ |
159 |
NSAlert *alert = [[NSAlert alloc] init]; |
160 |
[alert setMessageText:@"Create Directory Error"]; |
161 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", |
162 |
directoryPath, [error localizedDescription]]]; |
163 |
[alert addButtonWithTitle:@"OK"]; |
164 |
[alert runModal]; |
165 |
}); |
166 |
} |
167 |
|
168 |
for (ASIPithosObject *object in objects) { |
169 |
if ([self isContentTypeDirectory:object.contentType]) { |
170 |
NSString *subdirDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName]; |
171 |
subdirDirectoryPath = [subdirDirectoryPath stringByAppendingPathComponent:[object.name substringFromIndex:subdirPrefixLength]]; |
172 |
|
173 |
BOOL directoryIsDirectory; |
174 |
BOOL directoryExists = [fileManager fileExistsAtPath:subdirDirectoryPath isDirectory:&directoryIsDirectory]; |
175 |
NSError *error = nil; |
176 |
if (!directoryExists) { |
177 |
[fileManager createDirectoryAtPath:subdirDirectoryPath withIntermediateDirectories:YES attributes:nil error:&error]; |
178 |
if (error) { |
179 |
DLog(@"Cannot create directory at '%@': %@", subdirDirectoryPath, error); |
180 |
dispatch_async(dispatch_get_main_queue(), ^{ |
181 |
NSAlert *alert = [[NSAlert alloc] init]; |
182 |
[alert setMessageText:@"Create Directory Error"]; |
183 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot create directory at '%@': %@", |
184 |
subdirDirectoryPath, [error localizedDescription]]]; |
185 |
[alert addButtonWithTitle:@"OK"]; |
186 |
[alert runModal]; |
187 |
}); |
188 |
} |
189 |
} else if (!directoryIsDirectory) { |
190 |
[fileManager removeItemAtPath:subdirDirectoryPath error:&error]; |
191 |
if (error) { |
192 |
DLog(@"Cannot remove existing file at '%@': %@", subdirDirectoryPath, error); |
193 |
dispatch_async(dispatch_get_main_queue(), ^{ |
194 |
NSAlert *alert = [[NSAlert alloc] init]; |
195 |
[alert setMessageText:@"Remove File Error"]; |
196 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot remove existing file at '%@': %@", |
197 |
subdirDirectoryPath, [error localizedDescription]]]; |
198 |
[alert addButtonWithTitle:@"OK"]; |
199 |
[alert runModal]; |
200 |
}); |
201 |
} |
202 |
} |
203 |
} else { |
204 |
NSString *fileName = [object.name lastPathComponent]; |
205 |
if([object.name hasSuffix:@"/"]) |
206 |
fileName = [fileName stringByAppendingString:@"/"]; |
207 |
|
208 |
NSString *objectDirectoryPath = [directoryPath stringByAppendingPathComponent:subdirName]; |
209 |
objectDirectoryPath = [objectDirectoryPath stringByAppendingPathComponent:[object.name substringWithRange:NSMakeRange(subdirPrefixLength, [object.name length] - subdirPrefixLength - [fileName length])]]; |
210 |
|
211 |
ASIPithosObjectRequest *objectRequest = [self objectDataRequestWithPithos:pithos |
212 |
containerName:containerName |
213 |
objectName:object.name |
214 |
version:nil |
215 |
toDirectory:objectDirectoryPath |
216 |
withNewFileName:nil |
217 |
checkIfExists:NO |
218 |
sharingAccount:sharingAccount]; |
219 |
[(NSMutableDictionary *)objectRequest.userInfo setObject:[NSNumber numberWithUnsignedInteger:object.bytes] forKey:@"bytes"]; |
220 |
[objectRequests addObject:objectRequest]; |
221 |
} |
222 |
} |
223 |
|
224 |
return objectRequests; |
225 |
} |
226 |
|
227 |
#pragma mark - |
228 |
#pragma mark Download Block |
229 |
|
230 |
+ (ASIPithosObjectRequest *)objectBlockDataRequestWithPithos:(ASIPithos *)pithos |
231 |
containerName:(NSString *)containerName |
232 |
object:(ASIPithosObject *)object |
233 |
blockIndex:(NSUInteger)blockIndex |
234 |
blockSize:(NSUInteger)blockSize { |
235 |
NSUInteger rangeStart = blockIndex * blockSize; |
236 |
NSUInteger rangeEnd = (rangeStart + blockSize <= object.bytes) ? (rangeStart + blockSize - 1) : (object.bytes - 1); |
237 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectDataRequestWithPithos:pithos |
238 |
containerName:containerName |
239 |
objectName:object.name |
240 |
version:nil |
241 |
range:[NSString stringWithFormat:@"bytes=%lu-%lu", rangeStart, rangeEnd] |
242 |
ifMatch:object.hash]; |
243 |
return objectRequest; |
244 |
} |
245 |
|
246 |
+ (NSIndexSet *)missingBlocksForFile:(NSString *)filePath |
247 |
blockSize:(NSUInteger)blockSize |
248 |
blockHash:(NSString *)blockHash |
249 |
withHashes:(NSArray *)hashes { |
250 |
NSArray *fileHashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; |
251 |
return [hashes indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) { |
252 |
if ((idx >= [fileHashes count]) || ![(NSString *)obj isEqualToString:[fileHashes objectAtIndex:idx]]) |
253 |
return YES; |
254 |
return NO; |
255 |
}]; |
256 |
} |
257 |
|
258 |
#pragma mark - |
259 |
#pragma mark Upload |
260 |
|
261 |
+ (ASIPithosObjectRequest *)writeObjectDataRequestWithPithos:(ASIPithos *)pithos |
262 |
containerName:(NSString *)containerName |
263 |
objectName:(NSString *)objectName |
264 |
contentType:(NSString *)contentType |
265 |
blockSize:(NSUInteger)blockSize |
266 |
blockHash:(NSString *)blockHash |
267 |
forFile:(NSString *)filePath |
268 |
checkIfExists:(BOOL)ifExists |
269 |
hashes:(NSArray **)hashes |
270 |
sharingAccount:(NSString *)sharingAccount { |
271 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName |
272 |
sharingAccount:(NSString *)sharingAccount]) |
273 |
return nil; |
274 |
|
275 |
if (*hashes == nil) |
276 |
*hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; |
277 |
if (*hashes == nil) |
278 |
return nil; |
279 |
NSString *fileName = [filePath lastPathComponent]; |
280 |
if ([filePath hasSuffix:@"/"]) |
281 |
fileName = [fileName stringByAppendingString:@"/"]; |
282 |
NSUInteger bytes = [self bytesOfFile:filePath]; |
283 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
284 |
containerName:containerName |
285 |
objectName:objectName |
286 |
contentType:contentType |
287 |
contentEncoding:nil |
288 |
contentDisposition:nil |
289 |
manifest:nil |
290 |
sharing:nil |
291 |
isPublic:ASIPithosObjectRequestPublicIgnore |
292 |
metadata:nil |
293 |
blockSize:blockSize |
294 |
blockHash:blockHash |
295 |
hashes:*hashes |
296 |
bytes:bytes]; |
297 |
if (sharingAccount) |
298 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
299 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
300 |
fileName, @"fileName", |
301 |
[NSNumber numberWithUnsignedInteger:bytes], @"bytes", |
302 |
nil]; |
303 |
return objectRequest; |
304 |
} |
305 |
|
306 |
+ (NSIndexSet *)missingBlocksForHashes:(NSArray *)hashes withMissingHashes:(NSArray *)missingHashes { |
307 |
NSMutableIndexSet *missingBlocks = [NSMutableIndexSet indexSet]; |
308 |
for (NSString *missingHash in missingHashes) { |
309 |
if (![missingHash length]) |
310 |
break; |
311 |
NSUInteger missingBlock = [hashes indexOfObject:missingHash]; |
312 |
if (missingBlock != NSNotFound) |
313 |
[missingBlocks addIndex:missingBlock]; |
314 |
} |
315 |
return missingBlocks; |
316 |
} |
317 |
|
318 |
+ (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos |
319 |
containerName:(NSString *)containerName |
320 |
blockSize:(NSUInteger)blockSize |
321 |
forFile:(NSString *)filePath |
322 |
hashes:(NSArray *)hashes |
323 |
missingHashes:(NSArray *)missingHashes |
324 |
sharingAccount:(NSString *)sharingAccount { |
325 |
NSIndexSet *missingBlocks = [self missingBlocksForHashes:hashes withMissingHashes:missingHashes]; |
326 |
|
327 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
328 |
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; |
329 |
|
330 |
// http://cocoawithlove.com/2009/07/temporary-files-and-folders-in-cocoa.html |
331 |
NSString *tempFileTemplate = NSTemporaryDirectory(); |
332 |
if (tempFileTemplate == nil) |
333 |
tempFileTemplate = @"/tmp"; |
334 |
tempFileTemplate = [tempFileTemplate stringByAppendingPathComponent:@"pithos-macos.XXXXXX"]; |
335 |
const char *tempFileTemplateCString = [tempFileTemplate fileSystemRepresentation]; |
336 |
char *tempFileNameCString = (char *)malloc(strlen(tempFileTemplateCString) + 1); |
337 |
strcpy(tempFileNameCString, tempFileTemplateCString); |
338 |
int fileDescriptor = mkstemp(tempFileNameCString); |
339 |
NSString *tempFilePath = [fileManager stringWithFileSystemRepresentation:tempFileNameCString length:strlen(tempFileNameCString)]; |
340 |
free(tempFileNameCString); |
341 |
if (fileDescriptor == -1) { |
342 |
dispatch_async(dispatch_get_main_queue(), ^{ |
343 |
NSAlert *alert = [[NSAlert alloc] init]; |
344 |
[alert setMessageText:@"Create Temporary File Error"]; |
345 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot create temporary file at '%@'", tempFilePath]]; |
346 |
[alert addButtonWithTitle:@"OK"]; |
347 |
[alert runModal]; |
348 |
}); |
349 |
return nil; |
350 |
} |
351 |
NSFileHandle *tempFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:fileDescriptor closeOnDealloc:YES]; |
352 |
|
353 |
[missingBlocks enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop){ |
354 |
[fileHandle seekToFileOffset:(idx*blockSize)]; |
355 |
[tempFileHandle writeData:[fileHandle readDataOfLength:blockSize]]; |
356 |
}]; |
357 |
[tempFileHandle closeFile]; |
358 |
[fileHandle closeFile]; |
359 |
|
360 |
ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos |
361 |
containerName:containerName |
362 |
policy:nil |
363 |
metadata:nil |
364 |
update:YES |
365 |
file:tempFilePath]; |
366 |
if (sharingAccount) |
367 |
[containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
368 |
return containerRequest; |
369 |
} |
370 |
|
371 |
+ (ASIPithosContainerRequest *)updateContainerDataRequestWithPithos:(ASIPithos *)pithos |
372 |
containerName:(NSString *)containerName |
373 |
blockSize:(NSUInteger)blockSize |
374 |
forFile:(NSString *)filePath |
375 |
missingBlockIndex:(NSUInteger)missingBlockIndex |
376 |
sharingAccount:(NSString *)sharingAccount { |
377 |
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:filePath]; |
378 |
[fileHandle seekToFileOffset:(missingBlockIndex *blockSize)]; |
379 |
NSData *blockData = [fileHandle readDataOfLength:blockSize]; |
380 |
[fileHandle closeFile]; |
381 |
ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest updateContainerDataRequestWithPithos:pithos |
382 |
containerName:containerName |
383 |
policy:nil |
384 |
metadata:nil |
385 |
update:YES |
386 |
data:blockData]; |
387 |
if (sharingAccount) |
388 |
[containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
389 |
return containerRequest; |
390 |
} |
391 |
|
392 |
+ (NSArray *)writeObjectDataRequestsWithPithos:(ASIPithos *)pithos |
393 |
containerName:(NSString *)containerName |
394 |
objectName:(NSString *)objectName |
395 |
blockSize:(NSUInteger)blockSize |
396 |
blockHash:(NSString *)blockHash |
397 |
forDirectory:(NSString *)directoryPath |
398 |
checkIfExists:(BOOL)ifExists |
399 |
objectNames:(NSMutableArray **)objectNames |
400 |
contentTypes:(NSMutableArray **)contentTypes |
401 |
filePaths:(NSMutableArray **)filePaths |
402 |
hashesArrays:(NSMutableArray **)hashesArrays |
403 |
directoryObjectRequests:(NSMutableArray **) directoryObjectRequests |
404 |
sharingAccount:(NSString *)sharingAccount { |
405 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:containerName objectName:objectName |
406 |
sharingAccount:sharingAccount]) |
407 |
return nil; |
408 |
|
409 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
410 |
NSError *error = nil; |
411 |
NSArray *subPaths = [fileManager subpathsOfDirectoryAtPath:directoryPath error:&error]; |
412 |
if (error) { |
413 |
dispatch_async(dispatch_get_main_queue(), ^{ |
414 |
NSAlert *alert = [[NSAlert alloc] init]; |
415 |
[alert setMessageText:@"Directory Read Error"]; |
416 |
[alert setInformativeText:[NSString stringWithFormat:@"Cannot read contents of directory '%@': %@", |
417 |
[directoryPath lastPathComponent], [error localizedDescription]]]; |
418 |
[alert addButtonWithTitle:@"OK"]; |
419 |
[alert runModal]; |
420 |
}); |
421 |
return nil; |
422 |
} |
423 |
|
424 |
*directoryObjectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
425 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
426 |
containerName:containerName |
427 |
objectName:objectName |
428 |
eTag:nil |
429 |
contentType:@"application/directory" |
430 |
contentEncoding:nil |
431 |
contentDisposition:nil |
432 |
manifest:nil |
433 |
sharing:nil |
434 |
isPublic:ASIPithosObjectRequestPublicIgnore |
435 |
metadata:nil |
436 |
data:[NSData data]]; |
437 |
if (sharingAccount) |
438 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
439 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
440 |
[directoryPath lastPathComponent], @"fileName", |
441 |
nil]; |
442 |
[*directoryObjectRequests addObject:objectRequest]; |
443 |
|
444 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
445 |
*objectNames = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
446 |
*contentTypes = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
447 |
*filePaths = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
448 |
*hashesArrays = [NSMutableArray arrayWithCapacity:[subPaths count]]; |
449 |
BOOL isDirectory; |
450 |
NSString *subObjectName; |
451 |
NSArray *hashes; |
452 |
NSUInteger bytes; |
453 |
NSString *contentType; |
454 |
NSString *filePath; |
455 |
NSString *fileName; |
456 |
for (NSString *objectNameSuffix in subPaths) { |
457 |
filePath = [directoryPath stringByAppendingPathComponent:objectNameSuffix]; |
458 |
if ([fileManager fileExistsAtPath:filePath isDirectory:&isDirectory]) { |
459 |
if (!isDirectory) { |
460 |
hashes = [HashMapHash objectHashMapStrings:filePath withBlockHash:blockHash andBlockSize:blockSize]; |
461 |
if (hashes) { |
462 |
subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping]; |
463 |
fileName = [filePath lastPathComponent]; |
464 |
if ([filePath hasSuffix:@"/"]) |
465 |
fileName = [fileName stringByAppendingString:@"/"]; |
466 |
bytes = [self bytesOfFile:filePath]; |
467 |
error = nil; |
468 |
contentType = [self contentTypeOfFile:filePath error:&error]; |
469 |
if (contentType == nil) |
470 |
contentType = @"application/octet-stream"; |
471 |
#if DEBUG_PITHOS |
472 |
if (error) |
473 |
DLog(@"contentType detection error: %@", error); |
474 |
#endif |
475 |
objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
476 |
containerName:containerName |
477 |
objectName:subObjectName |
478 |
contentType:contentType |
479 |
contentEncoding:nil |
480 |
contentDisposition:nil |
481 |
manifest:nil |
482 |
sharing:nil |
483 |
isPublic:ASIPithosObjectRequestPublicIgnore |
484 |
metadata:nil |
485 |
blockSize:blockSize |
486 |
blockHash:blockHash |
487 |
hashes:hashes |
488 |
bytes:bytes]; |
489 |
if (sharingAccount) |
490 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
491 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
492 |
fileName, @"fileName", |
493 |
[NSNumber numberWithUnsignedInteger:bytes], @"bytes", |
494 |
nil]; |
495 |
[objectRequests addObject:objectRequest]; |
496 |
[*objectNames addObject:subObjectName]; |
497 |
[*contentTypes addObject:contentType]; |
498 |
[*filePaths addObject:filePath]; |
499 |
[*hashesArrays addObject:hashes]; |
500 |
} |
501 |
|
502 |
} else { |
503 |
subObjectName = [[objectName stringByAppendingPathComponent:objectNameSuffix] precomposedStringWithCanonicalMapping]; |
504 |
fileName = [filePath lastPathComponent]; |
505 |
objectRequest = [ASIPithosObjectRequest writeObjectDataRequestWithPithos:pithos |
506 |
containerName:containerName |
507 |
objectName:subObjectName |
508 |
eTag:nil |
509 |
contentType:@"application/directory" |
510 |
contentEncoding:nil |
511 |
contentDisposition:nil |
512 |
manifest:nil |
513 |
sharing:nil |
514 |
isPublic:ASIPithosObjectRequestPublicIgnore |
515 |
metadata:nil |
516 |
data:[NSData data]]; |
517 |
if (sharingAccount) |
518 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
519 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
520 |
fileName, @"fileName", |
521 |
nil]; |
522 |
[*directoryObjectRequests addObject:objectRequest]; |
523 |
} |
524 |
} |
525 |
} |
526 |
|
527 |
return objectRequests; |
528 |
} |
529 |
|
530 |
#pragma mark - |
531 |
#pragma mark Delete |
532 |
|
533 |
+ (NSArray *)deleteObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos |
534 |
containerName:(NSString *)containerName |
535 |
objectName:(NSString *)objectName { |
536 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName delimiter:nil |
537 |
sharingAccount:nil]; |
538 |
if (objects == nil) |
539 |
return nil; |
540 |
|
541 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; |
542 |
ASIPithosObjectRequest *objectRequest; |
543 |
if (![objectName hasSuffix:@"/"]) { |
544 |
objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:objectName]; |
545 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
546 |
[objectName lastPathComponent], @"fileName", |
547 |
nil]; |
548 |
[objectRequests addObject:objectRequest]; |
549 |
} |
550 |
NSString *fileName; |
551 |
for (ASIPithosObject *object in objects) { |
552 |
fileName = [object.name lastPathComponent]; |
553 |
if ([object.name hasSuffix:@"/"]) |
554 |
fileName = [fileName stringByAppendingString:@"/"]; |
555 |
objectRequest = [ASIPithosObjectRequest deleteObjectRequestWithPithos:pithos containerName:containerName objectName:object.name]; |
556 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
557 |
fileName, @"fileName", |
558 |
nil]; |
559 |
[objectRequests addObject:objectRequest]; |
560 |
} |
561 |
|
562 |
if ([objectRequests count] == 0) |
563 |
return nil; |
564 |
return objectRequests; |
565 |
} |
566 |
|
567 |
#pragma mark - |
568 |
#pragma mark Copy |
569 |
|
570 |
+ (ASIPithosObjectRequest *)cpyObjectRequestWithPithos:(ASIPithos *)pithos |
571 |
containerName:(NSString *)containerName |
572 |
objectName:(NSString *)objectName |
573 |
destinationContainerName:(NSString *)destinationContainerName |
574 |
destinationObjectName:(NSString *)destinationObjectName |
575 |
checkIfExists:(BOOL)ifExists |
576 |
sharingAccount:(NSString *)sharingAccount { |
577 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
578 |
sharingAccount:nil]) |
579 |
return nil; |
580 |
|
581 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos |
582 |
containerName:containerName |
583 |
objectName:objectName |
584 |
contentType:nil |
585 |
contentEncoding:nil |
586 |
contentDisposition:nil |
587 |
manifest:nil |
588 |
sharing:nil |
589 |
isPublic:ASIPithosObjectRequestPublicIgnore |
590 |
metadata:nil |
591 |
destinationContainerName:destinationContainerName |
592 |
destinationObjectName:destinationObjectName |
593 |
destinationAccount:nil |
594 |
sourceVersion:nil]; |
595 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
596 |
containerName, @"sourceContainerName", |
597 |
objectName, @"sourceObjectName", |
598 |
destinationContainerName, @"destinationContainerName", |
599 |
destinationObjectName, @"destinationObjectName", |
600 |
nil]; |
601 |
if (sharingAccount) |
602 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
603 |
return objectRequest; |
604 |
} |
605 |
|
606 |
+ (NSArray *)cpyObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos |
607 |
containerName:(NSString *)containerName |
608 |
objectName:(NSString *)objectName |
609 |
destinationContainerName:(NSString *)destinationContainerName |
610 |
destinationObjectName:(NSString *)destinationObjectName |
611 |
checkIfExists:(BOOL)ifExists |
612 |
sharingAccount:(NSString *)sharingAccount { |
613 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
614 |
sharingAccount:nil]) |
615 |
return nil; |
616 |
|
617 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName |
618 |
delimiter:nil sharingAccount:sharingAccount]; |
619 |
if (objects == nil) |
620 |
return nil; |
621 |
|
622 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; |
623 |
ASIPithosObjectRequest *objectRequest; |
624 |
if ([objectName isEqualToString:destinationObjectName]) { |
625 |
if (![objectName hasSuffix:@"/"]) { |
626 |
objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos |
627 |
containerName:containerName |
628 |
objectName:objectName |
629 |
contentType:nil |
630 |
contentEncoding:nil |
631 |
contentDisposition:nil |
632 |
manifest:nil |
633 |
sharing:nil |
634 |
isPublic:ASIPithosObjectRequestPublicIgnore |
635 |
metadata:nil |
636 |
destinationContainerName:destinationContainerName |
637 |
destinationObjectName:objectName |
638 |
destinationAccount:nil |
639 |
sourceVersion:nil]; |
640 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
641 |
containerName, @"sourceContainerName", |
642 |
objectName, @"sourceObjectName", |
643 |
destinationContainerName, @"destinationContainerName", |
644 |
objectName, @"destinationObjectName", |
645 |
nil]; |
646 |
if (sharingAccount) |
647 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
648 |
[objectRequests addObject:objectRequest]; |
649 |
} |
650 |
for (ASIPithosObject *object in objects) { |
651 |
objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos |
652 |
containerName:containerName |
653 |
objectName:object.name |
654 |
contentType:nil |
655 |
contentEncoding:nil |
656 |
contentDisposition:nil |
657 |
manifest:nil |
658 |
sharing:nil |
659 |
isPublic:ASIPithosObjectRequestPublicIgnore |
660 |
metadata:nil |
661 |
destinationContainerName:destinationContainerName |
662 |
destinationObjectName:object.name |
663 |
destinationAccount:nil |
664 |
sourceVersion:nil]; |
665 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
666 |
containerName, @"sourceContainerName", |
667 |
object.name, @"sourceObjectName", |
668 |
destinationContainerName, @"destinationContainerName", |
669 |
object.name, @"destinationObjectName", |
670 |
nil]; |
671 |
if (sharingAccount) |
672 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
673 |
[objectRequests addObject:objectRequest]; |
674 |
} |
675 |
} else { |
676 |
if (![objectName hasSuffix:@"/"]) { |
677 |
objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos |
678 |
containerName:containerName |
679 |
objectName:objectName |
680 |
contentType:nil |
681 |
contentEncoding:nil |
682 |
contentDisposition:nil |
683 |
manifest:nil |
684 |
sharing:nil |
685 |
isPublic:ASIPithosObjectRequestPublicIgnore |
686 |
metadata:nil |
687 |
destinationContainerName:destinationContainerName |
688 |
destinationObjectName:destinationObjectName |
689 |
destinationAccount:nil |
690 |
sourceVersion:nil]; |
691 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
692 |
containerName, @"sourceContainerName", |
693 |
objectName, @"sourceObjectName", |
694 |
destinationContainerName, @"destinationContainerName", |
695 |
destinationObjectName, @"destinationObjectName", |
696 |
nil]; |
697 |
if (sharingAccount) |
698 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
699 |
[objectRequests addObject:objectRequest]; |
700 |
} |
701 |
NSRange prefixRange = NSMakeRange(0, [objectName length]); |
702 |
NSString *newObjectName; |
703 |
for (ASIPithosObject *object in objects) { |
704 |
newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName |
705 |
withString:destinationObjectName |
706 |
options:NSAnchoredSearch |
707 |
range:prefixRange]; |
708 |
objectRequest = [ASIPithosObjectRequest cpyObjectDataRequestWithPithos:pithos |
709 |
containerName:containerName |
710 |
objectName:object.name |
711 |
contentType:nil |
712 |
contentEncoding:nil |
713 |
contentDisposition:nil |
714 |
manifest:nil |
715 |
sharing:nil |
716 |
isPublic:ASIPithosObjectRequestPublicIgnore |
717 |
metadata:nil |
718 |
destinationContainerName:destinationContainerName |
719 |
destinationObjectName:newObjectName |
720 |
destinationAccount:nil |
721 |
sourceVersion:nil]; |
722 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
723 |
containerName, @"sourceContainerName", |
724 |
object.name, @"sourceObjectName", |
725 |
destinationContainerName, @"destinationContainerName", |
726 |
newObjectName, @"destinationObjectName", |
727 |
nil]; |
728 |
if (sharingAccount) |
729 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
730 |
[objectRequests addObject:objectRequest]; |
731 |
} |
732 |
} |
733 |
|
734 |
if ([objectRequests count] == 0) |
735 |
return nil; |
736 |
return objectRequests; |
737 |
} |
738 |
|
739 |
#pragma mark - |
740 |
#pragma mark Move |
741 |
|
742 |
+ (ASIPithosObjectRequest *)moveObjectRequestWithPithos:(ASIPithos *)pithos |
743 |
containerName:(NSString *)containerName |
744 |
objectName:(NSString *)objectName |
745 |
destinationContainerName:(NSString *)destinationContainerName |
746 |
destinationObjectName:(NSString *)destinationObjectName |
747 |
checkIfExists:(BOOL)ifExists { |
748 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
749 |
sharingAccount:nil]) |
750 |
return nil; |
751 |
|
752 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
753 |
containerName:containerName |
754 |
objectName:objectName |
755 |
contentType:nil |
756 |
contentEncoding:nil |
757 |
contentDisposition:nil |
758 |
manifest:nil |
759 |
sharing:nil |
760 |
isPublic:ASIPithosObjectRequestPublicIgnore |
761 |
metadata:nil |
762 |
destinationContainerName:destinationContainerName |
763 |
destinationObjectName:destinationObjectName |
764 |
destinationAccount:nil]; |
765 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
766 |
containerName, @"sourceContainerName", |
767 |
objectName, @"sourceObjectName", |
768 |
destinationContainerName, @"destinationContainerName", |
769 |
destinationObjectName, @"destinationObjectName", |
770 |
nil]; |
771 |
return objectRequest; |
772 |
} |
773 |
|
774 |
+ (NSArray *)moveObjectRequestsForSubdirWithPithos:(ASIPithos *)pithos |
775 |
containerName:(NSString *)containerName |
776 |
objectName:(NSString *)objectName |
777 |
destinationContainerName:(NSString *)destinationContainerName |
778 |
destinationObjectName:(NSString *)destinationObjectName |
779 |
checkIfExists:(BOOL)ifExists { |
780 |
if (ifExists && ![self proceedIfObjectExistsAtPithos:pithos containerName:destinationContainerName objectName:destinationObjectName |
781 |
sharingAccount:nil]) |
782 |
return nil; |
783 |
|
784 |
NSArray *objects = [self objectsForSubdirWithPithos:pithos containerName:containerName objectName:objectName |
785 |
delimiter:nil sharingAccount:nil]; |
786 |
if (objects == nil) |
787 |
return nil; |
788 |
|
789 |
ASIPithosObjectRequest *objectRequest; |
790 |
NSMutableArray *objectRequests = [NSMutableArray arrayWithCapacity:([objects count] + 1)]; |
791 |
if ([objectName isEqualToString:destinationObjectName]) { |
792 |
if (![objectName hasSuffix:@"/"]) { |
793 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
794 |
containerName:containerName |
795 |
objectName:objectName |
796 |
contentType:nil |
797 |
contentEncoding:nil |
798 |
contentDisposition:nil |
799 |
manifest:nil |
800 |
sharing:nil |
801 |
isPublic:ASIPithosObjectRequestPublicIgnore |
802 |
metadata:nil |
803 |
destinationContainerName:destinationContainerName |
804 |
destinationObjectName:objectName |
805 |
destinationAccount:nil]; |
806 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
807 |
containerName, @"sourceContainerName", |
808 |
objectName, @"sourceObjectName", |
809 |
destinationContainerName, @"destinationContainerName", |
810 |
objectName, @"destinationObjectName", |
811 |
nil]; |
812 |
[objectRequests addObject:objectRequest]; |
813 |
} |
814 |
for (ASIPithosObject *object in objects) { |
815 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
816 |
containerName:containerName |
817 |
objectName:object.name |
818 |
contentType:nil |
819 |
contentEncoding:nil |
820 |
contentDisposition:nil |
821 |
manifest:nil |
822 |
sharing:nil |
823 |
isPublic:ASIPithosObjectRequestPublicIgnore |
824 |
metadata:nil |
825 |
destinationContainerName:destinationContainerName |
826 |
destinationObjectName:object.name |
827 |
destinationAccount:nil]; |
828 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
829 |
containerName, @"sourceContainerName", |
830 |
object.name, @"sourceObjectName", |
831 |
destinationContainerName, @"destinationContainerName", |
832 |
object.name, @"destinationObjectName", |
833 |
nil]; |
834 |
[objectRequests addObject:objectRequest]; |
835 |
} |
836 |
} else { |
837 |
if (![objectName hasSuffix:@"/"]) { |
838 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
839 |
containerName:containerName |
840 |
objectName:objectName |
841 |
contentType:nil |
842 |
contentEncoding:nil |
843 |
contentDisposition:nil |
844 |
manifest:nil |
845 |
sharing:nil |
846 |
isPublic:ASIPithosObjectRequestPublicIgnore |
847 |
metadata:nil |
848 |
destinationContainerName:destinationContainerName |
849 |
destinationObjectName:destinationObjectName |
850 |
destinationAccount:nil]; |
851 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
852 |
containerName, @"sourceContainerName", |
853 |
objectName, @"sourceObjectName", |
854 |
destinationContainerName, @"destinationContainerName", |
855 |
destinationObjectName, @"destinationObjectName", |
856 |
nil]; |
857 |
[objectRequests addObject:objectRequest]; |
858 |
} |
859 |
NSRange prefixRange = NSMakeRange(0, [objectName length]); |
860 |
NSString *newObjectName; |
861 |
for (ASIPithosObject *object in objects) { |
862 |
newObjectName = [object.name stringByReplacingOccurrencesOfString:objectName |
863 |
withString:destinationObjectName |
864 |
options:NSAnchoredSearch |
865 |
range:prefixRange]; |
866 |
objectRequest = [ASIPithosObjectRequest moveObjectDataRequestWithPithos:pithos |
867 |
containerName:containerName |
868 |
objectName:object.name |
869 |
contentType:nil |
870 |
contentEncoding:nil |
871 |
contentDisposition:nil |
872 |
manifest:nil |
873 |
sharing:nil |
874 |
isPublic:ASIPithosObjectRequestPublicIgnore |
875 |
metadata:nil |
876 |
destinationContainerName:destinationContainerName |
877 |
destinationObjectName:newObjectName |
878 |
destinationAccount:nil]; |
879 |
objectRequest.userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: |
880 |
containerName, @"sourceContainerName", |
881 |
object.name, @"sourceObjectName", |
882 |
destinationContainerName, @"destinationContainerName", |
883 |
newObjectName, @"destinationObjectName", |
884 |
nil]; |
885 |
[objectRequests addObject:objectRequest]; |
886 |
} |
887 |
} |
888 |
|
889 |
if ([objectRequests count] == 0) |
890 |
return nil; |
891 |
return objectRequests; |
892 |
} |
893 |
|
894 |
#pragma mark - |
895 |
#pragma mark Helper Methods |
896 |
|
897 |
// Size of the file in bytes |
898 |
+ (NSUInteger)bytesOfFile:(NSString *)filePath { |
899 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
900 |
NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil]; |
901 |
return [[attributes objectForKey:NSFileSize] unsignedIntegerValue]; |
902 |
} |
903 |
|
904 |
// Content type of the file or nil if it cannot be determined |
905 |
+ (NSString *)contentTypeOfFile:(NSString *)filePath error:(NSError **)error { |
906 |
// Based on http://www.ddeville.me/2011/12/mime-to-UTI-cocoa/ |
907 |
// and Apple example ImageBrowserViewAppearance/ImageBrowserController.m |
908 |
LSItemInfoRecord info; |
909 |
CFStringRef uti = NULL; |
910 |
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, (CFStringRef)filePath, kCFURLPOSIXPathStyle, FALSE); |
911 |
if (LSCopyItemInfoForURL(url, kLSRequestExtension | kLSRequestTypeCreator, &info) == noErr) { |
912 |
// Obtain the UTI using the file information. |
913 |
// If there is a file extension, get the UTI. |
914 |
if (info.extension != NULL) { |
915 |
uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, info.extension, kUTTypeData); |
916 |
CFRelease(info.extension); |
917 |
} |
918 |
// No UTI yet |
919 |
if (uti == NULL) { |
920 |
// If there is an OSType, get the UTI. |
921 |
CFStringRef typeString = UTCreateStringForOSType(info.filetype); |
922 |
if ( typeString != NULL) { |
923 |
uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassOSType, typeString, kUTTypeData); |
924 |
CFRelease(typeString); |
925 |
} |
926 |
} |
927 |
if (uti != NULL) { |
928 |
CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType); |
929 |
CFRelease(uti); |
930 |
CFRelease(url); |
931 |
return (__bridge_transfer NSString *)MIMEType; |
932 |
} |
933 |
} |
934 |
CFRelease(url); |
935 |
return nil; |
936 |
} |
937 |
|
938 |
// Creates a directory if it doesn't exist and returns if successful |
939 |
+ (BOOL)safeCreateDirectory:(NSString *)directoryPath error:(NSError **)error { |
940 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
941 |
BOOL isDirectory; |
942 |
BOOL fileExists = [fileManager fileExistsAtPath:directoryPath isDirectory:&isDirectory]; |
943 |
if (fileExists) |
944 |
return isDirectory; |
945 |
if (![fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:error] || *error) |
946 |
return NO; |
947 |
return YES; |
948 |
} |
949 |
|
950 |
// Removes contents of a directory and the directory itself if selected |
951 |
+ (void)removeContentsAtPath:(NSString *)dirPath andDirectory:(BOOL)removeDirectory { |
952 |
NSFileManager *fileManager = [NSFileManager defaultManager]; |
953 |
NSError *error = nil; |
954 |
BOOL isDirectory; |
955 |
if (![fileManager fileExistsAtPath:dirPath isDirectory:&isDirectory]) |
956 |
return; |
957 |
if (isDirectory) { |
958 |
for (NSString *subPath in [fileManager contentsOfDirectoryAtPath:dirPath error:&error]) { |
959 |
if (error) { |
960 |
[self fileActionFailedAlertWithTitle:@"Directory Contents Error" |
961 |
message:[NSString stringWithFormat:@"Cannot get contents of directory at '%@'", dirPath] |
962 |
error:error]; |
963 |
break; |
964 |
} |
965 |
NSString *subFilePath = [dirPath stringByAppendingPathComponent:subPath]; |
966 |
if (![fileManager removeItemAtPath:subFilePath error:&error] || error) { |
967 |
[self fileActionFailedAlertWithTitle:@"Remove File Error" |
968 |
message:[NSString stringWithFormat:@"Cannot remove file at '%@'", subFilePath] |
969 |
error:error]; |
970 |
} |
971 |
error = nil; |
972 |
} |
973 |
if (removeDirectory && (![fileManager removeItemAtPath:dirPath error:&error] || error)) { |
974 |
[self fileActionFailedAlertWithTitle:@"Remove Directory Error" |
975 |
message:[NSString stringWithFormat:@"Cannot remove directory at '%@'", dirPath] |
976 |
error:error]; |
977 |
} |
978 |
} else if (![fileManager removeItemAtPath:dirPath error:&error] || error) { |
979 |
[self fileActionFailedAlertWithTitle:@"Remove File Error" |
980 |
message:[NSString stringWithFormat:@"Cannot remove file at '%@'", dirPath] |
981 |
error:error]; |
982 |
} |
983 |
} |
984 |
|
985 |
// Removes contents of a directory |
986 |
+ (void)removeContentsAtPath:(NSString *)dirPath { |
987 |
[self removeContentsAtPath:dirPath andDirectory:NO]; |
988 |
} |
989 |
|
990 |
// Returns if an object is a directory based on its content type |
991 |
+ (BOOL)isContentTypeDirectory:(NSString *)contentType { |
992 |
return ([contentType isEqualToString:@"application/directory"] || |
993 |
[contentType hasPrefix:@"application/directory;"] || |
994 |
[contentType isEqualToString:@"application/folder"] || |
995 |
[contentType hasPrefix:@"application/folder;"]); |
996 |
} |
997 |
|
998 |
// Returns if an object exists at the given container/object path and if this object is an application/directory |
999 |
// If an error occured an alert is shown and it is returned so the caller won't proceed |
1000 |
+ (BOOL)objectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName |
1001 |
error:(NSError **)error isDirectory:(BOOL *)isDirectory sharingAccount:(NSString *)sharingAccount { |
1002 |
ASIPithosObjectRequest *objectRequest = [ASIPithosObjectRequest objectMetadataRequestWithPithos:pithos |
1003 |
containerName:containerName |
1004 |
objectName:objectName]; |
1005 |
if (sharingAccount) |
1006 |
[objectRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
1007 |
[self startAndWaitForRequest:objectRequest]; |
1008 |
if (error != NULL) { |
1009 |
*error = [objectRequest error]; |
1010 |
if (*error) { |
1011 |
[self httpRequestErrorAlertWithRequest:objectRequest]; |
1012 |
return NO; |
1013 |
} else if (objectRequest.responseStatusCode == 200) { |
1014 |
*isDirectory = [self isContentTypeDirectory:[objectRequest contentType]]; |
1015 |
return YES; |
1016 |
} |
1017 |
} |
1018 |
return NO; |
1019 |
} |
1020 |
|
1021 |
// Returns if the caller should proceed, after an interactive check if an object exists |
1022 |
// at the given container/object path is performed |
1023 |
+ (BOOL)proceedIfObjectExistsAtPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName |
1024 |
sharingAccount:(NSString *)sharingAccount { |
1025 |
NSError *error = nil; |
1026 |
BOOL isDirectory; |
1027 |
BOOL objectExists = [self objectExistsAtPithos:pithos containerName:containerName objectName:objectName |
1028 |
error:&error isDirectory:&isDirectory sharingAccount:sharingAccount]; |
1029 |
if (error) { |
1030 |
return NO; |
1031 |
} else if (objectExists) { |
1032 |
__block NSInteger choice; |
1033 |
dispatch_sync(dispatch_get_main_queue(), ^{ |
1034 |
NSAlert *alert = [[NSAlert alloc] init]; |
1035 |
if (isDirectory) { |
1036 |
[alert setMessageText:@"Directory Exists"]; |
1037 |
if (sharingAccount) |
1038 |
[alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]]; |
1039 |
else |
1040 |
[alert setInformativeText:[NSString stringWithFormat:@"A directory with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]]; |
1041 |
} else { |
1042 |
[alert setMessageText:@"Object Exists"]; |
1043 |
if (sharingAccount) |
1044 |
[alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' of user '%@' already exists, do you want to replace it?", objectName, containerName, sharingAccount]]; |
1045 |
else |
1046 |
[alert setInformativeText:[NSString stringWithFormat:@"An object with path '%@' in the container '%@' already exists, do you want to replace it?", objectName, containerName]]; |
1047 |
} |
1048 |
[alert addButtonWithTitle:@"OK"]; |
1049 |
[alert addButtonWithTitle:@"Cancel"]; |
1050 |
choice = [alert runModal]; |
1051 |
}); |
1052 |
if (choice == NSAlertSecondButtonReturn) |
1053 |
return NO; |
1054 |
} |
1055 |
return YES; |
1056 |
} |
1057 |
|
1058 |
// List of objects at the given container/object path, with prefix and or delimiter |
1059 |
+ (NSArray *)objectsWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectNamePrefix:(NSString *)objectNamePrefix |
1060 |
delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { |
1061 |
NSMutableArray *objects = [NSMutableArray array]; |
1062 |
NSString *marker = nil; |
1063 |
do { |
1064 |
ASIPithosContainerRequest *containerRequest = [ASIPithosContainerRequest listObjectsRequestWithPithos:pithos |
1065 |
containerName:containerName |
1066 |
limit:0 |
1067 |
marker:marker |
1068 |
prefix:objectNamePrefix |
1069 |
delimiter:delimiter |
1070 |
path:nil |
1071 |
meta:nil |
1072 |
shared:NO |
1073 |
until:nil]; |
1074 |
if (sharingAccount) |
1075 |
[containerRequest setRequestUserFromDefaultTo:sharingAccount withPithos:pithos]; |
1076 |
[self startAndWaitForRequest:containerRequest]; |
1077 |
if ([containerRequest error]) { |
1078 |
[self httpRequestErrorAlertWithRequest:containerRequest]; |
1079 |
return nil; |
1080 |
} |
1081 |
NSArray *someObjects = [containerRequest objects]; |
1082 |
[objects addObjectsFromArray:someObjects]; |
1083 |
if ([someObjects count] < 10000) |
1084 |
marker = nil; |
1085 |
else |
1086 |
marker = [[someObjects lastObject] name]; |
1087 |
} while (marker); |
1088 |
return objects; |
1089 |
} |
1090 |
|
1091 |
// List of objects at the given container/object path, that may be a subdir or an application/directory, |
1092 |
// with prefix and or delimiter |
1093 |
+ (NSArray *)objectsForSubdirWithPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName |
1094 |
delimiter:(NSString *)delimiter sharingAccount:(NSString *)sharingAccount { |
1095 |
NSString *subdirNamePrefix = [NSString stringWithString:objectName]; |
1096 |
if (![subdirNamePrefix hasSuffix:@"/"]) |
1097 |
subdirNamePrefix = [subdirNamePrefix stringByAppendingString:@"/"]; |
1098 |
return [self objectsWithPithos:pithos containerName:containerName objectNamePrefix:subdirNamePrefix |
1099 |
delimiter:delimiter sharingAccount:sharingAccount]; |
1100 |
} |
1101 |
|
1102 |
// A safe object name at the given container/object path |
1103 |
// The original name has " %d" appended to it before any ".*" suffix, for the first integer that produces a name that is free to use |
1104 |
// If the original name hasn't got a "." but has a "/" suffix, then it is replaced with " %d/" instead |
1105 |
// Subdirs are taken into consideration |
1106 |
+ (NSString *)safeObjectNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName objectName:(NSString *)objectName { |
1107 |
NSString *objectNamePrefix; |
1108 |
NSString *objectNameExtraSuffix; |
1109 |
NSRange lastDotRange = [objectName rangeOfString:@"." options:NSBackwardsSearch]; |
1110 |
if (lastDotRange.length == 1) { |
1111 |
objectNamePrefix = [objectName substringToIndex:lastDotRange.location]; |
1112 |
objectNameExtraSuffix = [objectName substringFromIndex:lastDotRange.location]; |
1113 |
} else if ([objectName hasSuffix:@"/"]) { |
1114 |
objectNamePrefix = [objectName substringToIndex:([objectName length] - 1)]; |
1115 |
objectNameExtraSuffix = @"/"; |
1116 |
} else { |
1117 |
objectNamePrefix = [NSString stringWithString:objectName]; |
1118 |
objectNameExtraSuffix = [NSString string]; |
1119 |
} |
1120 |
NSArray *objects = [self objectsWithPithos:pithos containerName:containerName |
1121 |
objectNamePrefix:[objectNamePrefix stringByDeletingLastPathComponent] |
1122 |
delimiter:@"/" sharingAccount:nil]; |
1123 |
if (objects == nil) |
1124 |
return nil; |
1125 |
if ([objects count] == 0) |
1126 |
return objectName; |
1127 |
NSMutableArray *objectNames = [NSMutableArray arrayWithArray: |
1128 |
[[objects objectsAtIndexes: |
1129 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1130 |
if (pithosObject.subdir) |
1131 |
return NO; |
1132 |
return YES; |
1133 |
}]] valueForKey:@"name"]]; |
1134 |
for (NSString *name in [[objects objectsAtIndexes: |
1135 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1136 |
if (pithosObject.subdir) |
1137 |
return YES; |
1138 |
return NO; |
1139 |
}]] valueForKey:@"name"]) { |
1140 |
[objectNames addObject:[name substringToIndex:([name length] - 1)]]; |
1141 |
} |
1142 |
if (![objectNames containsObject:objectName]) |
1143 |
return objectName; |
1144 |
NSUInteger objectNameSuffix = 2; |
1145 |
NSString *safeObjectName; |
1146 |
do { |
1147 |
safeObjectName = [objectNamePrefix stringByAppendingFormat:@" %lu%@", objectNameSuffix, objectNameExtraSuffix]; |
1148 |
objectNameSuffix++; |
1149 |
} while ([objectNames containsObject:safeObjectName]); |
1150 |
return safeObjectName; |
1151 |
} |
1152 |
|
1153 |
// A safe object name at the given container/object path that may be a subdir or application/directory |
1154 |
// The original name has " %d" appended to it, for the first integer that produces a name that is free to use |
1155 |
// If the original name has a "/" suffix, then it is replaced with " %d/" instead |
1156 |
// Subdirs are taken into consideration |
1157 |
+ (NSString *)safeSubdirNameForPithos:(ASIPithos *)pithos containerName:(NSString *)containerName subdirName:(NSString *)subdirName { |
1158 |
NSString *subdirNamePrefix; |
1159 |
NSString *subdirNameExtraSuffix; |
1160 |
if ([subdirName hasSuffix:@"/"]) { |
1161 |
subdirNamePrefix = [subdirName substringToIndex:([subdirName length] - 1)]; |
1162 |
subdirNameExtraSuffix = @"/"; |
1163 |
} else { |
1164 |
subdirNamePrefix = [NSString stringWithString:subdirName]; |
1165 |
subdirNameExtraSuffix = [NSString string]; |
1166 |
} |
1167 |
NSArray *objects = [self objectsWithPithos:pithos containerName:containerName |
1168 |
objectNamePrefix:[subdirNamePrefix stringByDeletingLastPathComponent] |
1169 |
delimiter:@"/" sharingAccount:nil]; |
1170 |
if (objects == nil) |
1171 |
return nil; |
1172 |
if ([objects count] == 0) |
1173 |
return subdirName; |
1174 |
NSMutableArray *objectNames = [NSMutableArray arrayWithArray: |
1175 |
[[objects objectsAtIndexes: |
1176 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1177 |
if (pithosObject.subdir) |
1178 |
return NO; |
1179 |
return YES; |
1180 |
}]] valueForKey:@"name"]]; |
1181 |
for (NSString *name in [[objects objectsAtIndexes: |
1182 |
[objects indexesOfObjectsPassingTest:^(ASIPithosObject *pithosObject, NSUInteger idx, BOOL *stop){ |
1183 |
if (pithosObject.subdir) |
1184 |
return YES; |
1185 |
return NO; |
1186 |
}]] valueForKey:@"name"]) { |
1187 |
[objectNames addObject:[name substringToIndex:([name length] - 1)]]; |
1188 |
} |
1189 |
if (![objectNames containsObject:subdirNamePrefix]) |
1190 |
return subdirName; |
1191 |
NSUInteger subdirNameSuffix = 2; |
1192 |
NSString *safeSubdirName; |
1193 |
do { |
1194 |
safeSubdirName = [subdirNamePrefix stringByAppendingFormat:@" %lu", subdirNameSuffix]; |
1195 |
subdirNameSuffix++; |
1196 |
} while ([objectNames containsObject:safeSubdirName]); |
1197 |
return [safeSubdirName stringByAppendingString:subdirNameExtraSuffix]; |
1198 |
} |
1199 |
|
1200 |
#pragma mark - |
1201 |
#pragma mark Alerts |
1202 |
|
1203 |
+ (void)httpRequestErrorAlertWithRequest:(ASIPithosRequest *)request { |
1204 |
if (request.responseStatusCode == 401) { |
1205 |
[self httpAuthenticationError]; |
1206 |
return; |
1207 |
} |
1208 |
NSString *message = [NSString stringWithFormat: |
1209 |
@"HTTP request error: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", |
1210 |
[[request error] localizedDescription], |
1211 |
request.requestMethod, |
1212 |
request.url, |
1213 |
[request requestHeaders], |
1214 |
[request responseHeaders], |
1215 |
[request responseString]]; |
1216 |
DLog(@"%@", message); |
1217 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1218 |
@autoreleasepool { |
1219 |
NSAlert *alert = [[NSAlert alloc] init]; |
1220 |
[alert setMessageText:@"HTTP Request Error"]; |
1221 |
[alert setInformativeText:message]; |
1222 |
[alert addButtonWithTitle:@"OK"]; |
1223 |
[alert runModal]; |
1224 |
} |
1225 |
}); |
1226 |
} |
1227 |
|
1228 |
+ (void)unexpectedResponseStatusAlertWithRequest:(ASIPithosRequest *)request { |
1229 |
if (request.responseStatusCode == 401) { |
1230 |
[self httpAuthenticationError]; |
1231 |
return; |
1232 |
} |
1233 |
NSString *message = [NSString stringWithFormat: |
1234 |
@"Unexpected response status %d: %@\n%@ URL: %@\nRequest Headers: %@\nResponse Headers: %@\nResponse String: %@", |
1235 |
request.responseStatusCode, |
1236 |
request.responseStatusMessage, |
1237 |
request.requestMethod, |
1238 |
request.url, |
1239 |
[request requestHeaders], |
1240 |
[request responseHeaders], |
1241 |
[request responseString]]; |
1242 |
DLog(@"%@", message); |
1243 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1244 |
@autoreleasepool { |
1245 |
NSAlert *alert = [[NSAlert alloc] init]; |
1246 |
[alert setMessageText:@"Unexpected Response Status"]; |
1247 |
[alert setInformativeText:message]; |
1248 |
[alert addButtonWithTitle:@"OK"]; |
1249 |
[alert runModal]; |
1250 |
} |
1251 |
}); |
1252 |
} |
1253 |
|
1254 |
+ (void)httpAuthenticationError { |
1255 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1256 |
@autoreleasepool { |
1257 |
NSAlert *alert = [[NSAlert alloc] init]; |
1258 |
[alert setMessageText:@"Authentication Error"]; |
1259 |
[alert setInformativeText:@"Authentication error, please check your token or login again"]; |
1260 |
[alert addButtonWithTitle:@"OK"]; |
1261 |
[alert runModal]; |
1262 |
} |
1263 |
}); |
1264 |
} |
1265 |
|
1266 |
+ (void)fileActionFailedAlertWithTitle:(NSString *)title message:(NSString *)message error:(NSError *)error { |
1267 |
dispatch_async(dispatch_get_main_queue(), ^{ |
1268 |
@autoreleasepool { |
1269 |
NSAlert *alert = [[NSAlert alloc] init]; |
1270 |
[alert setMessageText:title]; |
1271 |
if (error) |
1272 |
[alert setInformativeText:[NSString stringWithFormat:@"%@: %@", message, [error localizedDescription]]]; |
1273 |
else |
1274 |
[alert setInformativeText:message]; |
1275 |
[alert addButtonWithTitle:@"OK"]; |
1276 |
[alert runModal]; |
1277 |
} |
1278 |
}); |
1279 |
} |
1280 |
|
1281 |
#pragma mark - |
1282 |
#pragma mark Request Helper Methods |
1283 |
|
1284 |
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request priority:(NSOperationQueuePriority)priority { |
1285 |
request.timeOutSeconds = 60; |
1286 |
request.numberOfTimesToRetryOnTimeout = 10; |
1287 |
request.queuePriority = priority; |
1288 |
return request; |
1289 |
} |
1290 |
|
1291 |
+ (ASIPithosRequest *)prepareRequest:(ASIPithosRequest *)request { |
1292 |
return [self prepareRequest:request priority:NSOperationQueuePriorityNormal]; |
1293 |
} |
1294 |
|
1295 |
+ (ASIPithosRequest *)copyRequest:(ASIPithosRequest *)request { |
1296 |
NSMutableDictionary *userInfo = (NSMutableDictionary *)request.userInfo; |
1297 |
request.userInfo = nil; |
1298 |
ASIPithosRequest *newRequest = [request copy]; |
1299 |
newRequest.userInfo = userInfo; |
1300 |
return newRequest; |
1301 |
} |
1302 |
|
1303 |
+ (void)startAndWaitForRequest:(ASIPithosRequest *)request { |
1304 |
ASINetworkQueue *networkQueue = [ASINetworkQueue queue]; |
1305 |
[networkQueue go]; |
1306 |
[networkQueue addOperations:[NSArray arrayWithObject:[self prepareRequest:request]] waitUntilFinished:YES]; |
1307 |
} |
1308 |
|
1309 |
+ (ASIPithosRequest *)retryWithUpdatedURLRequest:(ASIPithosRequest *)request andPithosAccountManager:(PithosAccount *)pithosAccountManager { |
1310 |
ASIPithosRequest *newRequest = [PithosUtilities copyRequest:request]; |
1311 |
if (((request.responseStatusCode == 401) || (request.responseStatusCode == 404) || (request.responseStatusCode == 0)) && |
1312 |
request.retryBaseURLString) { |
1313 |
ASIPithosRequest *serviceCatalogRequest = [ASIPithosRequest serviceCatalogRequestWithPithos:pithosAccountManager.pithos]; |
1314 |
[PithosUtilities startAndWaitForRequest:serviceCatalogRequest]; |
1315 |
[pithosAccountManager updateServicesFromServiceCatalogRequest:serviceCatalogRequest]; |
1316 |
|
1317 |
if (newRequest.retryType == ASIPithosRequestTypeStorage) { |
1318 |
NSString *URLPrefix = pithosAccountManager.pithos.storageURLPrefix; |
1319 |
NSString *URLSuffix = [[newRequest.url description] substringFromIndex:[newRequest.retryBaseURLString length]]; |
1320 |
newRequest.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", URLPrefix, URLSuffix]]; |
1321 |
} else if (newRequest.retryType == ASIPithosRequestTypeUserCatalog) { |
1322 |
NSString *URLPrefix = pithosAccountManager.pithos.userCatalogURL; |
1323 |
NSString *URLSuffix = [[newRequest.url description] substringFromIndex:[newRequest.retryBaseURLString length]]; |
1324 |
newRequest.url = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", URLPrefix, URLSuffix]]; |
1325 |
} |
1326 |
} |
1327 |
|
1328 |
return newRequest; |
1329 |
} |
1330 |
|
1331 |
@end |