Ben Copsey

Added support for S3 list requests

Added more tests, bug fixes
  1 +//
  2 +// ASIS3BucketObject.h
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 13/07/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import <Foundation/Foundation.h>
  10 +@class ASIS3Request;
  11 +
  12 +@interface ASIS3BucketObject : NSObject {
  13 +
  14 + // The bucket this object belongs to
  15 + NSString *bucket;
  16 +
  17 + // The key (path) of this object in the bucket
  18 + NSString *key;
  19 +
  20 + // When this object was last modified
  21 + NSDate *lastModified;
  22 +
  23 + // The ETag for this object's content
  24 + NSString *ETag;
  25 +
  26 + // The size in bytes of this object
  27 + unsigned long long size;
  28 +
  29 + // Info about the owner
  30 + NSString *ownerID;
  31 + NSString *ownerName;
  32 +}
  33 +
  34 ++ (id)objectWithBucket:(NSString *)bucket;
  35 +
  36 +// Returns a request that will fetch this object when run
  37 +- (ASIS3Request *)GETRequest;
  38 +
  39 +// Returns a request that will replace this object with the contents of the file at filePath when run
  40 +- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath;
  41 +
  42 +// Returns a request that will delete this object when run
  43 +- (ASIS3Request *)DELETERequest;
  44 +
  45 +@property (retain) NSString *bucket;
  46 +@property (retain) NSString *key;
  47 +@property (retain) NSDate *lastModified;
  48 +@property (retain) NSString *ETag;
  49 +@property (assign) unsigned long long size;
  50 +@property (retain) NSString *ownerID;
  51 +@property (retain) NSString *ownerName;
  52 +@end
  1 +//
  2 +// ASIS3BucketObject.m
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 13/07/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import "ASIS3BucketObject.h"
  10 +#import "ASIS3Request.h"
  11 +
  12 +@implementation ASIS3BucketObject
  13 +
  14 ++ (id)objectWithBucket:(NSString *)bucket
  15 +{
  16 + ASIS3BucketObject *object = [[[ASIS3BucketObject alloc] init] autorelease];
  17 + [object setBucket:bucket];
  18 + return object;
  19 +}
  20 +
  21 +- (void)dealloc
  22 +{
  23 + [key release];
  24 + [lastModified release];
  25 + [ETag release];
  26 + [ownerID release];
  27 + [ownerName release];
  28 + [super dealloc];
  29 +}
  30 +
  31 +- (ASIS3Request *)GETRequest
  32 +{
  33 + return [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
  34 +}
  35 +
  36 +- (ASIS3Request *)PUTRequestWithFile:(NSString *)filePath
  37 +{
  38 + return [ASIS3Request PUTRequestForFile:filePath withBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
  39 +}
  40 +
  41 +- (ASIS3Request *)DELETERequest
  42 +{
  43 + ASIS3Request *request = [ASIS3Request requestWithBucket:[self bucket] path:[NSString stringWithFormat:@"/%@",[self key]]];
  44 + [request setRequestMethod:@"DELETE"];
  45 + return request;
  46 +}
  47 +
  48 +
  49 +- (NSString *)description
  50 +{
  51 + return [NSString stringWithFormat:@"Key: %@ lastModified: %@ ETag: %@ size: %llu ownerID: %@ ownerName: %@",[self key],[self lastModified],[self ETag],[self size],[self ownerID],[self ownerName]];
  52 +}
  53 +
  54 +@synthesize bucket;
  55 +@synthesize key;
  56 +@synthesize lastModified;
  57 +@synthesize ETag;
  58 +@synthesize size;
  59 +@synthesize ownerID;
  60 +@synthesize ownerName;
  61 +@end
  1 +//
  2 +// ASIS3ListRequest.h
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 13/07/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +
  9 +#import <Foundation/Foundation.h>
  10 +#import "ASIS3Request.h"
  11 +@class ASIS3BucketObject;
  12 +
  13 +@interface ASIS3ListRequest : ASIS3Request {
  14 +
  15 + NSString *prefix;
  16 + NSString *marker;
  17 + int maxResultCount;
  18 + NSString *delimiter;
  19 +
  20 + // Internally used while parsing the response
  21 + NSString *currentContent;
  22 + NSString *currentElement;
  23 + ASIS3BucketObject *currentObject;
  24 + NSMutableArray *objects;
  25 +
  26 +
  27 +}
  28 +// Create a list request
  29 ++ (id)listRequestWithBucket:(NSString *)bucket;
  30 +
  31 +
  32 +// Returns an array of ASIS3BucketObjects created from the XML response
  33 +- (NSArray *)bucketObjects;
  34 +
  35 +//Builds a query string out of the list parameters we supplied
  36 +- (void)createQueryString;
  37 +
  38 +@property (retain) NSString *prefix;
  39 +@property (retain) NSString *marker;
  40 +@property (assign) int maxResultCount;
  41 +@property (retain) NSString *delimiter;
  42 +@end
  1 +//
  2 +// ASIS3ListRequest.m
  3 +// Mac
  4 +//
  5 +// Created by Ben Copsey on 13/07/2009.
  6 +// Copyright 2009 All-Seeing Interactive. All rights reserved.
  7 +//
  8 +#import "ASIS3ListRequest.h"
  9 +#import "ASIS3BucketObject.h"
  10 +
  11 +
  12 +static NSDateFormatter *dateFormatter = nil;
  13 +
  14 +// Private stuff
  15 +@interface ASIS3ListRequest ()
  16 + @property (retain,setter=setURL:) NSURL *url;
  17 + @property (retain, nonatomic) NSString *currentContent;
  18 + @property (retain, nonatomic) NSString *currentElement;
  19 + @property (retain, nonatomic) ASIS3BucketObject *currentObject;
  20 + @property (retain, nonatomic) NSMutableArray *objects;
  21 +@end
  22 +
  23 +@implementation ASIS3ListRequest
  24 +
  25 ++ (void)initialize
  26 +{
  27 + dateFormatter = [[NSDateFormatter alloc] init];
  28 + [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"UTC"]];
  29 + [dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss'.000Z'"];
  30 +}
  31 +
  32 ++ (id)listRequestWithBucket:(NSString *)bucket
  33 +{
  34 + ASIS3ListRequest *request = [[[ASIS3ListRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com",bucket]]] autorelease];
  35 + [request setBucket:bucket];
  36 + return request;
  37 +}
  38 +
  39 +- (void)dealloc
  40 +{
  41 + [currentElement release];
  42 + [currentObject release];
  43 + [objects release];
  44 + [super dealloc];
  45 +}
  46 +
  47 +- (void)createQueryString
  48 +{
  49 + NSMutableArray *queryParts = [[[NSMutableArray alloc] init] autorelease];
  50 + if ([self prefix]) {
  51 + [queryParts addObject:[NSString stringWithFormat:@"prefix=%@",[[self prefix] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
  52 + }
  53 + if ([self marker]) {
  54 + [queryParts addObject:[NSString stringWithFormat:@"marker=%@",[[self marker] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
  55 + }
  56 + if ([self delimiter]) {
  57 + [queryParts addObject:[NSString stringWithFormat:@"delimiter=%@",[[self delimiter] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
  58 + }
  59 + if ([self maxResultCount] > 0) {
  60 + [queryParts addObject:[NSString stringWithFormat:@"delimiter=%hi",[self maxResultCount]]];
  61 + }
  62 + if ([queryParts count]) {
  63 + [self setURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@?%@",[[self url] absoluteString],[queryParts componentsJoinedByString:@"&"]]]];
  64 + }
  65 +}
  66 +
  67 +- (void)main
  68 +{
  69 + [self createQueryString];
  70 + [super main];
  71 +}
  72 +
  73 +- (NSArray *)bucketObjects
  74 +{
  75 + if ([self objects]) {
  76 + return [self objects];
  77 + }
  78 + [self setObjects:[[[NSMutableArray alloc] init] autorelease]];
  79 + NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
  80 + [parser setDelegate:self];
  81 + [parser setShouldProcessNamespaces:NO];
  82 + [parser setShouldReportNamespacePrefixes:NO];
  83 + [parser setShouldResolveExternalEntities:NO];
  84 + [parser parse];
  85 + return [self objects];
  86 +}
  87 +
  88 +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
  89 +{
  90 + [self setCurrentElement:elementName];
  91 +
  92 + if ([elementName isEqualToString:@"Contents"]) {
  93 + [self setCurrentObject:[ASIS3BucketObject objectWithBucket:[self bucket]]];
  94 + }
  95 + [self setCurrentContent:@""];
  96 +}
  97 +
  98 +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
  99 +{
  100 + if ([elementName isEqualToString:@"Contents"]) {
  101 + [objects addObject:currentObject];
  102 + [self setCurrentObject:nil];
  103 + } else if ([elementName isEqualToString:@"Key"]) {
  104 + [[self currentObject] setKey:[self currentContent]];
  105 + } else if ([elementName isEqualToString:@"LastModified"]) {
  106 + [[self currentObject] setLastModified:[dateFormatter dateFromString:[self currentContent]]];
  107 + } else if ([elementName isEqualToString:@"ETag"]) {
  108 + [[self currentObject] setETag:[self currentContent]];
  109 + } else if ([elementName isEqualToString:@"Size"]) {
  110 + [[self currentObject] setSize:(unsigned long long)[[self currentContent] longLongValue]];
  111 + } else if ([elementName isEqualToString:@"ID"]) {
  112 + [[self currentObject] setOwnerID:[self currentContent]];
  113 + } else if ([elementName isEqualToString:@"DisplayName"]) {
  114 + [[self currentObject] setOwnerName:[self currentContent]];
  115 + }
  116 +}
  117 +
  118 +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
  119 +{
  120 + [self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
  121 +}
  122 +
  123 +- (void)parserDidEndDocument:(NSXMLParser *)parser
  124 +{
  125 +}
  126 +
  127 +
  128 +@synthesize currentContent;
  129 +@synthesize currentElement;
  130 +@synthesize currentObject;
  131 +@synthesize objects;
  132 +@synthesize prefix;
  133 +@synthesize marker;
  134 +@synthesize maxResultCount;
  135 +@synthesize delimiter;
  136 +@synthesize url;
  137 +@end
@@ -4,8 +4,7 @@ @@ -4,8 +4,7 @@
4 // Created by Ben Copsey on 30/06/2009. 4 // Created by Ben Copsey on 30/06/2009.
5 // Copyright 2009 All-Seeing Interactive. All rights reserved. 5 // Copyright 2009 All-Seeing Interactive. All rights reserved.
6 // 6 //
7 -// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/) 7 +// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/) using the REST API
8 -// It uses the REST API, with canned access policies rather than full support for ACLs (though if you build/parse them yourself you can still use ACLs)  
9 8
10 #import <Foundation/Foundation.h> 9 #import <Foundation/Foundation.h>
11 #import "ASIHTTPRequest.h" 10 #import "ASIHTTPRequest.h"
@@ -16,6 +15,12 @@ extern NSString *const ASIS3AccessPolicyPublicRead; @@ -16,6 +15,12 @@ extern NSString *const ASIS3AccessPolicyPublicRead;
16 extern NSString *const ASIS3AccessPolicyPublicReadWrote; 15 extern NSString *const ASIS3AccessPolicyPublicReadWrote;
17 extern NSString *const ASIS3AccessPolicyAuthenticatedRead; 16 extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
18 17
  18 +typedef enum _ASIS3ErrorType {
  19 + ASIS3ResponseParsingFailedType = 1,
  20 + ASIS3ResponseErrorType = 2
  21 +
  22 +} ASIS3ErrorType;
  23 +
19 @interface ASIS3Request : ASIHTTPRequest { 24 @interface ASIS3Request : ASIHTTPRequest {
20 25
21 // Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:] 26 // Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:]
@@ -38,7 +43,19 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; @@ -38,7 +43,19 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
38 // Will be set to 'application/octet-stream' otherwise in iPhone apps, or autodetected on Mac OS X 43 // Will be set to 'application/octet-stream' otherwise in iPhone apps, or autodetected on Mac OS X
39 NSString *mimeType; 44 NSString *mimeType;
40 45
  46 + // The access policy to use when PUTting a file (see the string constants at the top of this header)
41 NSString *accessPolicy; 47 NSString *accessPolicy;
  48 +
  49 + // Options for filtering list requests
  50 + // See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTBucketGET.html
  51 + NSString *listPrefix;
  52 + NSString *listMarker;
  53 + int listMaxResults;
  54 + NSString *listDelimiter;
  55 +
  56 + // Internally used while parsing errors
  57 + NSString *currentErrorString;
  58 +
42 } 59 }
43 60
44 #pragma mark Constructors 61 #pragma mark Constructors
@@ -49,9 +66,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; @@ -49,9 +66,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
49 // Create a PUT request using the file at filePath as the body 66 // Create a PUT request using the file at filePath as the body
50 + (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path; 67 + (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path;
51 68
52 -// Create a list request  
53 -+ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker;  
54 -  
55 // Generates the request headers S3 needs 69 // Generates the request headers S3 needs
56 // Automatically called before the request begins in startRequest 70 // Automatically called before the request begins in startRequest
57 - (void)generateS3Headers; 71 - (void)generateS3Headers;
@@ -59,6 +73,8 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; @@ -59,6 +73,8 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
59 // Uses the supplied date to create a Date header string 73 // Uses the supplied date to create a Date header string
60 - (void)setDate:(NSDate *)date; 74 - (void)setDate:(NSDate *)date;
61 75
  76 +#pragma mark Helper functions
  77 +
62 // Only works on Mac OS, will always return 'application/octet-stream' on iPhone 78 // Only works on Mac OS, will always return 'application/octet-stream' on iPhone
63 + (NSString *)mimeTypeForFileAtPath:(NSString *)path; 79 + (NSString *)mimeTypeForFileAtPath:(NSString *)path;
64 80
@@ -77,5 +93,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead; @@ -77,5 +93,6 @@ extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
77 @property (retain) NSString *mimeType; 93 @property (retain) NSString *mimeType;
78 @property (retain) NSString *accessKey; 94 @property (retain) NSString *accessKey;
79 @property (retain) NSString *secretAccessKey; 95 @property (retain) NSString *secretAccessKey;
80 -@property (assign) NSString *accessPolicy; 96 +@property (retain) NSString *accessPolicy;
  97 +
81 @end 98 @end
@@ -17,9 +17,11 @@ static NSString *sharedAccessKey = nil; @@ -17,9 +17,11 @@ static NSString *sharedAccessKey = nil;
17 static NSString *sharedSecretAccessKey = nil; 17 static NSString *sharedSecretAccessKey = nil;
18 18
19 // Private stuff 19 // Private stuff
20 -@interface ASIHTTPRequest () 20 +@interface ASIS3Request ()
  21 + - (void)parseError;
21 + (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string; 22 + (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;
22 + (NSString *)base64forData:(NSData *)theData; 23 + (NSString *)base64forData:(NSData *)theData;
  24 + @property (retain, nonatomic) NSString *currentErrorString;
23 @end 25 @end
24 26
25 @implementation ASIS3Request 27 @implementation ASIS3Request
@@ -37,7 +39,7 @@ static NSString *sharedSecretAccessKey = nil; @@ -37,7 +39,7 @@ static NSString *sharedSecretAccessKey = nil;
37 39
38 + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path 40 + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path
39 { 41 {
40 - ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://s3.amazonaws.com/%@/%@",bucket,path]]] autorelease]; 42 + ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com%@",bucket,path]]] autorelease];
41 [request setBucket:bucket]; 43 [request setBucket:bucket];
42 [request setPath:path]; 44 [request setPath:path];
43 return request; 45 return request;
@@ -53,51 +55,16 @@ static NSString *sharedSecretAccessKey = nil; @@ -53,51 +55,16 @@ static NSString *sharedSecretAccessKey = nil;
53 return request; 55 return request;
54 } 56 }
55 57
56 -  
57 -+ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker  
58 -{  
59 - ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://s3.amazonaws.com/%@?prefix=/%@&max-keys=%hi&marker=%@",bucket,prefix,maxResults,marker]]] autorelease];  
60 - [request setBucket:bucket];  
61 - return request;  
62 -}  
63 -  
64 -+ (NSString *)mimeTypeForFileAtPath:(NSString *)path  
65 -{  
66 -// NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time.  
67 -#if TARGET_OS_IPHONE  
68 - return @"application/octet-stream";  
69 -  
70 -// Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type  
71 -// Perhaps there is a better way to do this?  
72 -#else  
73 - NSTask *task = [[NSTask alloc] init];  
74 - [task setLaunchPath: @"/usr/bin/file"];  
75 - [task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]];  
76 -  
77 - NSPipe *outputPipe = [NSPipe pipe];  
78 - [task setStandardOutput:outputPipe];  
79 -  
80 - NSFileHandle *file = [outputPipe fileHandleForReading];  
81 -  
82 - [task launch];  
83 - [task waitUntilExit];  
84 -  
85 - if ([task terminationStatus] != 0) {  
86 - return @"application/octet-stream";  
87 - }  
88 -  
89 - NSString *mimeTypeString = [[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease];  
90 - return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0];  
91 -#endif  
92 -}  
93 -  
94 - (void)setDate:(NSDate *)date 58 - (void)setDate:(NSDate *)date
95 { 59 {
96 NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; 60 NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
  61 + // Prevent problems with dates generated by other locales (tip from: http://rel.me/t/date/)
  62 + [dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]];
97 [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"]; 63 [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss Z"];
98 [self setDateString:[dateFormatter stringFromDate:date]]; 64 [self setDateString:[dateFormatter stringFromDate:date]];
99 } 65 }
100 66
  67 +
101 - (void)generateS3Headers 68 - (void)generateS3Headers
102 { 69 {
103 // If an access key / secret access keyu haven't been set for this request, let's use the shared keys 70 // If an access key / secret access keyu haven't been set for this request, let's use the shared keys
@@ -105,7 +72,7 @@ static NSString *sharedSecretAccessKey = nil; @@ -105,7 +72,7 @@ static NSString *sharedSecretAccessKey = nil;
105 [self setAccessKey:[ASIS3Request sharedAccessKey]]; 72 [self setAccessKey:[ASIS3Request sharedAccessKey]];
106 } 73 }
107 if (![self secretAccessKey]) { 74 if (![self secretAccessKey]) {
108 - [self setAccessKey:[ASIS3Request sharedSecretAccessKey]]; 75 + [self setSecretAccessKey:[ASIS3Request sharedSecretAccessKey]];
109 } 76 }
110 // If a date string hasn't been set, we'll create one from the current time 77 // If a date string hasn't been set, we'll create one from the current time
111 if (![self dateString]) { 78 if (![self dateString]) {
@@ -115,10 +82,10 @@ static NSString *sharedSecretAccessKey = nil; @@ -115,10 +82,10 @@ static NSString *sharedSecretAccessKey = nil;
115 82
116 // Ensure our formatted string doesn't use '(null)' for the empty path 83 // Ensure our formatted string doesn't use '(null)' for the empty path
117 if (![self path]) { 84 if (![self path]) {
118 - [self setPath:@""]; 85 + [self setPath:@"/"];
119 } 86 }
120 87
121 - NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@/%@",[self bucket],[self path]]; 88 + NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@%@",[self bucket],[self path]];
122 89
123 // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private) 90 // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private)
124 NSString *canonicalizedAmzHeaders = @""; 91 NSString *canonicalizedAmzHeaders = @"";
@@ -146,6 +113,59 @@ static NSString *sharedSecretAccessKey = nil; @@ -146,6 +113,59 @@ static NSString *sharedSecretAccessKey = nil;
146 [super main]; 113 [super main];
147 } 114 }
148 115
  116 +- (void)requestFinished
  117 +{
  118 + if ([self responseStatusCode] < 207) {
  119 + [super requestFinished];
  120 + return;
  121 + }
  122 + [self parseError];
  123 +}
  124 +
  125 +#pragma mark Error XML parsing
  126 +
  127 +- (void)parseError
  128 +{
  129 + NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
  130 + [parser setDelegate:self];
  131 + [parser setShouldProcessNamespaces:NO];
  132 + [parser setShouldReportNamespacePrefixes:NO];
  133 + [parser setShouldResolveExternalEntities:NO];
  134 + [parser parse];
  135 +
  136 +}
  137 +
  138 +- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
  139 +{
  140 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseParsingFailedType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"Parsing the resposnse failed",NSLocalizedDescriptionKey,parseError,NSUnderlyingErrorKey,nil]]];
  141 +}
  142 +
  143 +- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
  144 +{
  145 + [self setCurrentErrorString:@""];
  146 +}
  147 +
  148 +- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
  149 +{
  150 + if ([elementName isEqualToString:@"Message"]) {
  151 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[self currentErrorString],NSLocalizedDescriptionKey,nil]]];
  152 + }
  153 +}
  154 +
  155 +- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
  156 +{
  157 + [self setCurrentErrorString:[[self currentErrorString] stringByAppendingString:string]];
  158 +}
  159 +
  160 +- (void)parserDidEndDocument:(NSXMLParser *)parser
  161 +{
  162 + // We've got to the end of the XML error, without encountering a <Message></Message>, I don't think this should happen, but anyway
  163 + if (![self error]) {
  164 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIS3ResponseErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"An unspecified S3 error ocurred",NSLocalizedDescriptionKey,nil]]];
  165 + }
  166 +}
  167 +
  168 +
149 #pragma mark Shared access keys 169 #pragma mark Shared access keys
150 170
151 + (NSString *)sharedAccessKey 171 + (NSString *)sharedAccessKey
@@ -170,6 +190,38 @@ static NSString *sharedSecretAccessKey = nil; @@ -170,6 +190,38 @@ static NSString *sharedSecretAccessKey = nil;
170 sharedSecretAccessKey = [newAccessKey retain]; 190 sharedSecretAccessKey = [newAccessKey retain];
171 } 191 }
172 192
  193 +#pragma mark Helper functions
  194 +
  195 ++ (NSString *)mimeTypeForFileAtPath:(NSString *)path
  196 +{
  197 + // NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time.
  198 +#if TARGET_OS_IPHONE
  199 + return @"application/octet-stream";
  200 +
  201 + // Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type
  202 + // Perhaps there is a better way to do this?
  203 +#else
  204 + NSTask *task = [[[NSTask alloc] init] autorelease];
  205 + [task setLaunchPath: @"/usr/bin/file"];
  206 + [task setArguments:[NSMutableArray arrayWithObjects:@"-Ib",path,nil]];
  207 +
  208 + NSPipe *outputPipe = [NSPipe pipe];
  209 + [task setStandardOutput:outputPipe];
  210 +
  211 + NSFileHandle *file = [outputPipe fileHandleForReading];
  212 +
  213 + [task launch];
  214 + [task waitUntilExit];
  215 +
  216 + if ([task terminationStatus] != 0) {
  217 + return @"application/octet-stream";
  218 + }
  219 +
  220 + NSString *mimeTypeString = [[[[NSString alloc] initWithData:[file readDataToEndOfFile] encoding: NSUTF8StringEncoding] autorelease] stringByReplacingOccurrencesOfString:@"\n" withString:@""];
  221 + return [[mimeTypeString componentsSeparatedByString:@";"] objectAtIndex:0];
  222 +#endif
  223 +}
  224 +
173 225
174 #pragma mark S3 Authentication helpers 226 #pragma mark S3 Authentication helpers
175 227
@@ -231,4 +283,6 @@ static NSString *sharedSecretAccessKey = nil; @@ -231,4 +283,6 @@ static NSString *sharedSecretAccessKey = nil;
231 @synthesize accessKey; 283 @synthesize accessKey;
232 @synthesize secretAccessKey; 284 @synthesize secretAccessKey;
233 @synthesize accessPolicy; 285 @synthesize accessPolicy;
  286 +@synthesize currentErrorString;
  287 +
234 @end 288 @end
@@ -663,7 +663,7 @@ @@ -663,7 +663,7 @@
663 { 663 {
664 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]]; 664 ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_to_new_domain"]];
665 [request start]; 665 [request start];
666 - BOOL success = [[[request url] absoluteString] isEqualTo:@"http://www.apple.com/"]; 666 + BOOL success = [[[request url] absoluteString] isEqualToString:@"http://www.apple.com/"];
667 GHAssertTrue(success,@"Failed to redirect to a different domain"); 667 GHAssertTrue(success,@"Failed to redirect to a different domain");
668 } 668 }
669 669
@@ -13,10 +13,11 @@ @@ -13,10 +13,11 @@
13 #endif 13 #endif
14 14
15 @interface ASIS3RequestTests : GHTestCase { 15 @interface ASIS3RequestTests : GHTestCase {
16 -  
17 } 16 }
18 17
19 - (void)testAuthenticationHeaderGeneration; 18 - (void)testAuthenticationHeaderGeneration;
20 -//- (void)testREST; 19 +- (void)testREST;
  20 +- (void)testFailure;
  21 +- (void)testListRequest;
21 22
22 @end 23 @end
@@ -7,62 +7,68 @@ @@ -7,62 +7,68 @@
7 // 7 //
8 8
9 #import "ASIS3RequestTests.h" 9 #import "ASIS3RequestTests.h"
10 -#import "ASIS3Request.h" 10 +#import "ASIS3ListRequest.h"
  11 +#import "ASINetworkQueue.h"
  12 +#import "ASIS3BucketObject.h"
11 13
12 @implementation ASIS3RequestTests 14 @implementation ASIS3RequestTests
13 15
  16 +// Fill in these to run the tests that actually connect and manipulate objects on S3
  17 +static NSString *secretAccessKey = @"";
  18 +static NSString *accessKey = @"";
  19 +static NSString *bucket = @"";
14 20
  21 +// All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/
15 - (void)testAuthenticationHeaderGeneration 22 - (void)testAuthenticationHeaderGeneration
16 { 23 {
17 - // All these tests are based on Amazon's examples at: http://docs.amazonwebservices.com/AmazonS3/2006-03-01/ 24 + NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
18 - 25 + NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82";
19 - NSString *secretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";  
20 - NSString *accessKey = @"0PN5J17HBGZHT7JJ3X82";  
21 NSString *bucket = @"johnsmith"; 26 NSString *bucket = @"johnsmith";
22 27
23 -  
24 // Test GET 28 // Test GET
25 - NSString *path = @"photos/puppy.jpg"; 29 + NSString *path = @"/photos/puppy.jpg";
26 NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000"; 30 NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000";
27 ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path]; 31 ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
28 [request setDateString:dateString]; 32 [request setDateString:dateString];
29 - [request setSecretAccessKey:secretAccessKey]; 33 + [request setSecretAccessKey:exampleSecretAccessKey];
30 - [request setAccessKey:accessKey]; 34 + [request setAccessKey:exampleAccessKey];
31 [request generateS3Headers]; 35 [request generateS3Headers];
32 BOOL success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="]; 36 BOOL success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:xXjDGYUmKxnwqr5KXNPGldn5LbA="];
33 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET request"); 37 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a GET request");
34 38
35 // Test PUT 39 // Test PUT
36 - path = @"photos/puppy.jpg"; 40 + path = @"/photos/puppy.jpg";
37 dateString = @"Tue, 27 Mar 2007 21:15:45 +0000"; 41 dateString = @"Tue, 27 Mar 2007 21:15:45 +0000";
38 request = [ASIS3Request requestWithBucket:bucket path:path]; 42 request = [ASIS3Request requestWithBucket:bucket path:path];
39 [request setRequestMethod:@"PUT"]; 43 [request setRequestMethod:@"PUT"];
40 [request setMimeType:@"image/jpeg"]; 44 [request setMimeType:@"image/jpeg"];
41 [request setDateString:dateString]; 45 [request setDateString:dateString];
42 - [request setSecretAccessKey:secretAccessKey]; 46 + [request setSecretAccessKey:exampleSecretAccessKey];
43 - [request setAccessKey:accessKey]; 47 + [request setAccessKey:exampleAccessKey];
44 [request generateS3Headers]; 48 [request generateS3Headers];
45 success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="]; 49 success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:hcicpDDvL9SsO6AkvxqmIWkmOuQ="];
46 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a PUT request"); 50 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a PUT request");
47 51
48 // Test List 52 // Test List
49 - path = @"";  
50 dateString = @"Tue, 27 Mar 2007 19:42:41 +0000"; 53 dateString = @"Tue, 27 Mar 2007 19:42:41 +0000";
51 - request = [ASIS3Request listRequestWithBucket:bucket prefix:@"photos" maxResults:50 marker:@"puppy"]; 54 + ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
52 - [request setDateString:dateString]; 55 + [listRequest setPrefix:@"photos"];
53 - [request setSecretAccessKey:secretAccessKey]; 56 + [listRequest setMaxResultCount:50];
54 - [request setAccessKey:accessKey]; 57 + [listRequest setMarker:@"puppy"];
55 - [request generateS3Headers]; 58 + [listRequest setDateString:dateString];
56 - success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="]; 59 + [listRequest setSecretAccessKey:exampleSecretAccessKey];
  60 + [listRequest setAccessKey:exampleAccessKey];
  61 + [listRequest generateS3Headers];
  62 + success = [[[listRequest requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:jsRt/rhG+Vtp88HrYL706QhE4w4="];
57 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); 63 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
58 64
59 // Test fetch ACL 65 // Test fetch ACL
60 - path = @"?acl"; 66 + path = @"/?acl";
61 dateString = @"Tue, 27 Mar 2007 19:44:46 +0000"; 67 dateString = @"Tue, 27 Mar 2007 19:44:46 +0000";
62 request = [ASIS3Request requestWithBucket:bucket path:path]; 68 request = [ASIS3Request requestWithBucket:bucket path:path];
63 [request setDateString:dateString]; 69 [request setDateString:dateString];
64 - [request setSecretAccessKey:secretAccessKey]; 70 + [request setSecretAccessKey:exampleSecretAccessKey];
65 - [request setAccessKey:accessKey]; 71 + [request setAccessKey:exampleAccessKey];
66 [request generateS3Headers]; 72 [request generateS3Headers];
67 success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="]; 73 success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:thdUi9VAkzhkniLj96JIrOPGi0g="];
68 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); 74 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
@@ -70,25 +76,50 @@ @@ -70,25 +76,50 @@
70 // Test Unicode keys 76 // Test Unicode keys
71 // (I think Amazon's name for this example is misleading since this test actually only uses URL encoded strings) 77 // (I think Amazon's name for this example is misleading since this test actually only uses URL encoded strings)
72 bucket = @"dictionary"; 78 bucket = @"dictionary";
73 - path = @"fran%C3%A7ais/pr%c3%a9f%c3%a8re"; 79 + path = @"/fran%C3%A7ais/pr%c3%a9f%c3%a8re";
74 dateString = @"Wed, 28 Mar 2007 01:49:49 +0000"; 80 dateString = @"Wed, 28 Mar 2007 01:49:49 +0000";
75 request = [ASIS3Request requestWithBucket:bucket path:path]; 81 request = [ASIS3Request requestWithBucket:bucket path:path];
76 [request setDateString:dateString]; 82 [request setDateString:dateString];
77 - [request setSecretAccessKey:secretAccessKey]; 83 + [request setSecretAccessKey:exampleSecretAccessKey];
78 - [request setAccessKey:accessKey]; 84 + [request setAccessKey:exampleAccessKey];
79 [request generateS3Headers]; 85 [request generateS3Headers];
80 success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="]; 86 success = [[[request requestHeaders] valueForKey:@"Authorization"] isEqualToString:@"AWS 0PN5J17HBGZHT7JJ3X82:dxhSBHoI6eVSPcXJqEghlUzZMnY="];
81 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); 87 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
82 } 88 }
83 89
  90 +- (void)testFailure
  91 +{
  92 + // Needs expanding to cover more failure states - this is just a test to ensure Amazon's error description is being added to the error
  93 +
  94 + // We're actually going to try with the Amazon example details, but the request will fail because the date is old
  95 + NSString *exampleSecretAccessKey = @"uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o";
  96 + NSString *exampleAccessKey = @"0PN5J17HBGZHT7JJ3X82";
  97 + NSString *bucket = @"johnsmith";
  98 + NSString *path = @"/photos/puppy.jpg";
  99 + NSString *dateString = @"Tue, 27 Mar 2007 19:36:42 +0000";
  100 + ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];
  101 + [request setDateString:dateString];
  102 + [request setSecretAccessKey:exampleSecretAccessKey];
  103 + [request setAccessKey:exampleAccessKey];
  104 + [request start];
  105 + GHAssertNotNil([request error],@"Failed to generate an error when the request was not correctly signed");
  106 +
  107 + BOOL success = ([[request error] code] == ASIS3ResponseErrorType);
  108 + GHAssertTrue(success,@"Generated error had the wrong error code");
  109 +
  110 + success = ([[[request error] localizedDescription] isEqualToString:@"The difference between the request time and the current time is too large."]);
  111 + GHAssertTrue(success,@"Generated error had the wrong description");
  112 +
  113 +}
  114 +
84 // To run this test, uncomment and fill in your S3 access details 115 // To run this test, uncomment and fill in your S3 access details
85 -/*  
86 - (void)testREST 116 - (void)testREST
87 { 117 {
88 - NSString *secretAccessKey = @"my-secret-key"; 118 +
89 - NSString *accessKey = @"my-access-key"; 119 + BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]);
90 - NSString *bucket = @"bucket-name"; 120 + GHAssertTrue(success,@"You need to supply your S3 access details to run the REST test (see the top of ASIS3RequestTests.m)");
91 - NSString *path = @"path/to/file"; 121 +
  122 + NSString *path = @"/test";
92 123
93 // Create the fle 124 // Create the fle
94 NSString *text = @"This is my content"; 125 NSString *text = @"This is my content";
@@ -97,11 +128,10 @@ @@ -97,11 +128,10 @@
97 128
98 // PUT the file 129 // PUT the file
99 ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path]; 130 ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path];
100 - [request setRequestMethod:@"PUT"];  
101 [request setSecretAccessKey:secretAccessKey]; 131 [request setSecretAccessKey:secretAccessKey];
102 [request setAccessKey:accessKey]; 132 [request setAccessKey:accessKey];
103 [request start]; 133 [request start];
104 - BOOL success = [[request responseString] isEqualToString:@""]; 134 + success = [[request responseString] isEqualToString:@""];
105 GHAssertTrue(success,@"Failed to PUT a file to S3"); 135 GHAssertTrue(success,@"Failed to PUT a file to S3");
106 136
107 // GET the file 137 // GET the file
@@ -110,7 +140,17 @@ @@ -110,7 +140,17 @@
110 [request setAccessKey:accessKey]; 140 [request setAccessKey:accessKey];
111 [request start]; 141 [request start];
112 success = [[request responseString] isEqualToString:@"This is my content"]; 142 success = [[request responseString] isEqualToString:@"This is my content"];
113 - GHAssertTrue(success,@"Failed to GET the correct data from S3"); 143 + GHAssertTrue(success,@"Failed to GET the correct data from S3");
  144 +
  145 + // Get a list of files
  146 + ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
  147 + [listRequest setPrefix:@"test"];
  148 + [listRequest setSecretAccessKey:secretAccessKey];
  149 + [listRequest setAccessKey:accessKey];
  150 + [listRequest start];
  151 + GHAssertNil([listRequest error],@"Failed to download a list from S3");
  152 + success = [[listRequest bucketObjects] count];
  153 + GHAssertTrue(success,@"The file didn't show up in the list");
114 154
115 // DELETE the file 155 // DELETE the file
116 request = [ASIS3Request requestWithBucket:bucket path:path]; 156 request = [ASIS3Request requestWithBucket:bucket path:path];
@@ -121,7 +161,131 @@ @@ -121,7 +161,131 @@
121 success = [[request responseString] isEqualToString:@""]; 161 success = [[request responseString] isEqualToString:@""];
122 GHAssertTrue(success,@"Failed to DELETE the file from S3"); 162 GHAssertTrue(success,@"Failed to DELETE the file from S3");
123 } 163 }
124 -*/ 164 +
  165 +- (void)testListRequest
  166 +{
  167 +
  168 + BOOL success = (![secretAccessKey isEqualToString:@""] && ![accessKey isEqualToString:@""] && ![bucket isEqualToString:@""]);
  169 + GHAssertTrue(success,@"You need to supply your S3 access details to run the list test (see the top of ASIS3RequestTests.m)");
  170 +
  171 + // Firstly, create and upload 5 files
  172 + int i;
  173 + for (i=0; i<5; i++) {
  174 + NSString *text = [NSString stringWithFormat:@"This is the content of file #%hi",i];
  175 + NSString *filePath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]];
  176 + [[text dataUsingEncoding:NSUTF8StringEncoding] writeToFile:filePath atomically:NO];
  177 + NSString *path = [NSString stringWithFormat:@"/test-file/%hi",i];
  178 + ASIS3Request *request = [ASIS3Request PUTRequestForFile:filePath withBucket:bucket path:path];
  179 + [request setSecretAccessKey:secretAccessKey];
  180 + [request setAccessKey:accessKey];
  181 + [request start];
  182 + GHAssertNil([request error],@"Give up on list request test - failed to upload a file");
  183 + }
  184 +
  185 + // Now get a list of the files
  186 + ASIS3ListRequest *listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
  187 + [listRequest setPrefix:@"test-file"];
  188 + [listRequest setSecretAccessKey:secretAccessKey];
  189 + [listRequest setAccessKey:accessKey];
  190 + [listRequest start];
  191 + GHAssertNil([listRequest error],@"Failed to download a list from S3");
  192 + success = ([[listRequest bucketObjects] count] == 5);
  193 + GHAssertTrue(success,@"List did not contain all files");
  194 +
  195 + // Please don't use an autoreleased operation queue with waitUntilAllOperationsAreFinished in your own code unless you're writing a test like this one
  196 + // (The end result is no better than using synchronous requests) thx - Ben :)
  197 + ASINetworkQueue *queue = [[[ASINetworkQueue alloc] init] autorelease];
  198 +
  199 + // Test fetching all the items
  200 + [queue setRequestDidFinishSelector:@selector(GETRequestDone:)];
  201 + [queue setRequestDidFailSelector:@selector(GETRequestFailed:)];
  202 + [queue setDelegate:self];
  203 + for (ASIS3BucketObject *object in [listRequest bucketObjects]) {
  204 + ASIS3Request *request = [object GETRequest];
  205 + [request setAccessKey:accessKey];
  206 + [request setSecretAccessKey:secretAccessKey];
  207 + [queue addOperation:request];
  208 + }
  209 + [queue go];
  210 + [queue waitUntilAllOperationsAreFinished];
  211 +
  212 +
  213 + // Test uploading new files for all the items
  214 + [queue setRequestDidFinishSelector:@selector(PUTRequestDone:)];
  215 + [queue setRequestDidFailSelector:@selector(PUTRequestFailed:)];
  216 + [queue setDelegate:self];
  217 + i=0;
  218 + // For each one, we'll just upload the same content again
  219 + for (ASIS3BucketObject *object in [listRequest bucketObjects]) {
  220 + NSString *oldFilePath = [[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:[NSString stringWithFormat:@"%hi.txt",i]];;
  221 + ASIS3Request *request = [object PUTRequestWithFile:oldFilePath];
  222 + [request setAccessKey:accessKey];
  223 + [request setSecretAccessKey:secretAccessKey];
  224 + [queue addOperation:request];
  225 + i++;
  226 + }
  227 + [queue go];
  228 + [queue waitUntilAllOperationsAreFinished];
  229 +
  230 +
  231 + // Test deleting all the items
  232 + [queue setRequestDidFinishSelector:@selector(DELETERequestDone:)];
  233 + [queue setRequestDidFailSelector:@selector(DELETERequestFailed:)];
  234 + [queue setDelegate:self];
  235 + i=0;
  236 +
  237 + for (ASIS3BucketObject *object in [listRequest bucketObjects]) {
  238 + ASIS3Request *request = [object DELETERequest];
  239 + [request setAccessKey:accessKey];
  240 + [request setSecretAccessKey:secretAccessKey];
  241 + [queue addOperation:request];
  242 + i++;
  243 + }
  244 + [queue go];
  245 + [queue waitUntilAllOperationsAreFinished];
  246 +
  247 + // Grab the list again, it should be empty now
  248 + listRequest = [ASIS3ListRequest listRequestWithBucket:bucket];
  249 + [listRequest setPrefix:@"test-file"];
  250 + [listRequest setSecretAccessKey:secretAccessKey];
  251 + [listRequest setAccessKey:accessKey];
  252 + [listRequest start];
  253 + GHAssertNil([listRequest error],@"Failed to download a list from S3");
  254 + success = ([[listRequest bucketObjects] count] == 0);
  255 + GHAssertTrue(success,@"List contained files that should have been deleted");
  256 +
  257 +}
  258 +
  259 +- (void)GETRequestDone:(ASIS3Request *)request
  260 +{
  261 + NSString *expectedContent = [NSString stringWithFormat:@"This is the content of file #%@",[[[request url] absoluteString] lastPathComponent]];
  262 + BOOL success = ([[request responseString] isEqualToString:expectedContent]);
  263 + GHAssertTrue(success,@"Got the wrong content when downloading one of the files");
  264 +
  265 +}
  266 +
  267 +- (void)GETRequestFailed:(ASIS3Request *)request
  268 +{
  269 + GHAssertTrue(NO,@"GET request failed for one of the items in the list");
  270 +}
  271 +
  272 +- (void)PUTRequestDone:(ASIS3Request *)request
  273 +{
  274 +}
  275 +
  276 +- (void)PUTRequestFailed:(ASIS3Request *)request
  277 +{
  278 + GHAssertTrue(NO,@"PUT request failed for one of the items in the list");
  279 +}
  280 +
  281 +- (void)DELETERequestDone:(ASIS3Request *)request
  282 +{
  283 +}
  284 +
  285 +- (void)DELETERequestFailed:(ASIS3Request *)request
  286 +{
  287 + GHAssertTrue(NO,@"DELETE request failed for one of the items in the list");
  288 +}
125 289
126 290
127 @end 291 @end
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.