Ben Copsey

Merge branch 'cloudfiles' into integration

Conflicts:
	Classes/ASIHTTPRequest.m
	iPhone.xcodeproj/project.pbxproj
	set_version_number.sh
//
// ASICloudFilesCDNRequest.h
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASICloudFilesRequest.h"
@class ASICloudFilesContainerXMLParserDelegate;
@interface ASICloudFilesCDNRequest : ASICloudFilesRequest {
NSString *accountName;
NSString *containerName;
ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
}
@property (nonatomic, retain) NSString *accountName;
@property (nonatomic, retain) NSString *containerName;
@property (nonatomic, retain) ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
// HEAD /<api version>/<account>/<container>
// Response:
// X-CDN-Enabled: True
// X-CDN-URI: http://cdn.cloudfiles.mosso.com/c1234
// X-CDN-TTL: 86400
+ (id)containerInfoRequest:(NSString *)containerName;
- (BOOL)cdnEnabled;
- (NSString *)cdnURI;
- (NSUInteger)cdnTTL;
// GET /<api version>/<account>
// limit, marker, format, enabled_only=true
+ (id)listRequest;
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker enabledOnly:(BOOL)enabledOnly;
- (NSArray *)containers;
// PUT /<api version>/<account>/<container>
// PUT operations against a Container are used to CDN-enable that Container.
// Include an HTTP header of X-TTL to specify a custom TTL.
+ (id)putRequestWithContainer:(NSString *)containerName;
+ (id)putRequestWithContainer:(NSString *)containerName ttl:(NSUInteger)ttl;
// returns: - (NSString *)cdnURI;
// POST /<api version>/<account>/<container>
// POST operations against a CDN-enabled Container are used to adjust CDN attributes.
// The POST operation can be used to set a new TTL cache expiration or to enable/disable public sharing over the CDN.
// X-TTL: 86400
// X-CDN-Enabled: True
+ (id)postRequestWithContainer:(NSString *)containerName;
+ (id)postRequestWithContainer:(NSString *)containerName cdnEnabled:(BOOL)cdnEnabled ttl:(NSUInteger)ttl;
// returns: - (NSString *)cdnURI;
@end
... ...
//
// ASICloudFilesCDNRequest.m
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASICloudFilesCDNRequest.h"
#import "ASICloudFilesContainerXMLParserDelegate.h"
@implementation ASICloudFilesCDNRequest
@synthesize accountName, containerName, xmlParserDelegate;
+ (id)cdnRequestWithMethod:(NSString *)method query:(NSString *)query {
NSString *urlString = [NSString stringWithFormat:@"%@%@", [ASICloudFilesRequest cdnManagementURL], query];
ASICloudFilesCDNRequest *request = [[ASICloudFilesCDNRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setRequestMethod:method];
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
return request;
}
+ (id)cdnRequestWithMethod:(NSString *)method containerName:(NSString *)containerName {
NSString *urlString = [NSString stringWithFormat:@"%@/%@", [ASICloudFilesRequest cdnManagementURL], containerName];
ASICloudFilesCDNRequest *request = [[ASICloudFilesCDNRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setRequestMethod:method];
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
request.containerName = containerName;
return request;
}
#pragma mark -
#pragma mark HEAD - Container Info
+ (id)containerInfoRequest:(NSString *)containerName {
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"HEAD" containerName:containerName];
return request;
}
- (BOOL)cdnEnabled {
return [[[self responseHeaders] objectForKey:@"X-Cdn-Enabled"] boolValue];
}
- (NSString *)cdnURI {
return [[self responseHeaders] objectForKey:@"X-Cdn-Uri"];
}
- (NSUInteger)cdnTTL {
return [[[self responseHeaders] objectForKey:@"X-Ttl"] intValue];
}
#pragma mark -
#pragma mark GET - CDN Container Lists
+ (id)listRequest {
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"GET" query:@"?format=xml"];
return request;
}
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker enabledOnly:(BOOL)enabledOnly {
NSString *query = @"?format=xml";
if (limit > 0) {
query = [query stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
}
if (marker) {
query = [query stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", marker]];
}
if (limit > 0) {
query = [query stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
}
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"GET" query:query];
return request;
}
- (NSArray *)containers {
if (xmlParserDelegate.containerObjects) {
return xmlParserDelegate.containerObjects;
}
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
if (xmlParserDelegate == nil) {
xmlParserDelegate = [[ASICloudFilesContainerXMLParserDelegate alloc] init];
}
[parser setDelegate:xmlParserDelegate];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
return xmlParserDelegate.containerObjects;
}
#pragma mark -
#pragma mark PUT - CDN Enable Container
// PUT /<api version>/<account>/<container>
// PUT operations against a Container are used to CDN-enable that Container.
// Include an HTTP header of X-TTL to specify a custom TTL.
+ (id)putRequestWithContainer:(NSString *)containerName {
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"PUT" containerName:containerName];
return request;
}
+ (id)putRequestWithContainer:(NSString *)containerName ttl:(NSUInteger)ttl {
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"PUT" containerName:containerName];
[request addRequestHeader:@"X-Ttl" value:[NSString stringWithFormat:@"%i", ttl]];
return request;
}
#pragma mark -
#pragma mark POST - Adjust CDN Attributes
// POST /<api version>/<account>/<container>
// POST operations against a CDN-enabled Container are used to adjust CDN attributes.
// The POST operation can be used to set a new TTL cache expiration or to enable/disable public sharing over the CDN.
// X-TTL: 86400
// X-CDN-Enabled: True
+ (id)postRequestWithContainer:(NSString *)containerName {
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"POST" containerName:containerName];
return request;
}
+ (id)postRequestWithContainer:(NSString *)containerName cdnEnabled:(BOOL)cdnEnabled ttl:(NSUInteger)ttl {
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest cdnRequestWithMethod:@"POST" containerName:containerName];
if (ttl > 0) {
[request addRequestHeader:@"X-Ttl" value:[NSString stringWithFormat:@"%i", ttl]];
}
[request addRequestHeader:@"X-Cdn-Enabled" value:cdnEnabled ? @"True" : @"False"];
return request;
}
#pragma mark -
#pragma mark Memory Management
-(void)dealloc {
[accountName release];
[containerName release];
[xmlParserDelegate release];
[super dealloc];
}
@end
... ...
//
// ASICloudFilesContainer.h
//
// Created by Michael Mayo on 1/7/10.
//
#import <Foundation/Foundation.h>
@interface ASICloudFilesContainer : NSObject {
// regular container attributes
NSString *name;
NSUInteger count;
NSUInteger bytes;
// CDN container attributes
BOOL cdnEnabled;
NSUInteger ttl;
NSString *cdnURL;
BOOL logRetention;
NSString *referrerACL;
NSString *useragentACL;
}
+ (id)container;
// regular container attributes
@property (nonatomic, retain) NSString *name;
@property (nonatomic) NSUInteger count;
@property (nonatomic) NSUInteger bytes;
// CDN container attributes
@property (nonatomic) BOOL cdnEnabled;
@property (nonatomic) NSUInteger ttl;
@property (nonatomic, retain) NSString *cdnURL;
@property (nonatomic) BOOL logRetention;
@property (nonatomic, retain) NSString *referrerACL;
@property (nonatomic, retain) NSString *useragentACL;
@end
... ...
//
// ASICloudFilesContainer.m
//
// Created by Michael Mayo on 1/7/10.
//
#import "ASICloudFilesContainer.h"
@implementation ASICloudFilesContainer
// regular container attributes
@synthesize name, count, bytes;
// CDN container attributes
@synthesize cdnEnabled, ttl, cdnURL, logRetention, referrerACL, useragentACL;
+ (id)container {
ASICloudFilesContainer *container = [[[self alloc] init] autorelease];
return container;
}
-(void) dealloc {
[name release];
[super dealloc];
}
@end
... ...
//
// ASICloudFilesContainerRequest.h
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASICloudFilesRequest.h"
@class ASICloudFilesContainer, ASICloudFilesContainerXMLParserDelegate;
@interface ASICloudFilesContainerRequest : ASICloudFilesRequest {
// Internally used while parsing the response
NSString *currentContent;
NSString *currentElement;
ASICloudFilesContainer *currentObject;
ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
}
@property (nonatomic, retain) NSString *currentElement;
@property (nonatomic, retain) NSString *currentContent;
@property (nonatomic, retain) ASICloudFilesContainer *currentObject;
@property (nonatomic, retain) ASICloudFilesContainerXMLParserDelegate *xmlParserDelegate;
// HEAD /<api version>/<account>
// HEAD operations against an account are performed to retrieve the number of Containers and the total bytes stored in Cloud Files for the account. This information is returned in two custom headers, X-Account-Container-Count and X-Account-Bytes-Used.
+ (id)accountInfoRequest;
- (NSUInteger)containerCount;
- (NSUInteger)bytesUsed;
// GET /<api version>/<account>/<container>
// Create a request to list all containers
+ (id)listRequest;
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker;
- (NSArray *)containers;
// PUT /<api version>/<account>/<container>
+ (id)createContainerRequest:(NSString *)containerName;
// DELETE /<api version>/<account>/<container>
+ (id)deleteContainerRequest:(NSString *)containerName;
@end
... ...
//
// ASICloudFilesContainerRequest.m
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASICloudFilesContainerRequest.h"
#import "ASICloudFilesContainer.h"
#import "ASICloudFilesContainerXMLParserDelegate.h"
@implementation ASICloudFilesContainerRequest
@synthesize currentElement, currentContent, currentObject;
@synthesize xmlParserDelegate;
#pragma mark -
#pragma mark Constructors
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName queryString:(NSString *)queryString {
NSString *urlString;
if (containerName == nil) {
urlString = [NSString stringWithFormat:@"%@%@", [ASICloudFilesRequest storageURL], queryString];
} else {
urlString = [NSString stringWithFormat:@"%@/%@%@", [ASICloudFilesRequest storageURL], containerName, queryString];
}
ASICloudFilesContainerRequest *request = [[ASICloudFilesContainerRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setRequestMethod:method];
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
return request;
}
+ (id)storageRequestWithMethod:(NSString *)method queryString:(NSString *)queryString {
return [ASICloudFilesContainerRequest storageRequestWithMethod:method containerName:nil queryString:queryString];
}
+ (id)storageRequestWithMethod:(NSString *)method {
return [ASICloudFilesContainerRequest storageRequestWithMethod:method queryString:@""];
}
#pragma mark -
#pragma mark HEAD - Retrieve Container Count and Total Bytes Used
// HEAD /<api version>/<account>
// HEAD operations against an account are performed to retrieve the number of Containers and the total bytes stored in Cloud Files for the account. This information is returned in two custom headers, X-Account-Container-Count and X-Account-Bytes-Used.
+ (id)accountInfoRequest {
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"HEAD"];
return request;
}
- (NSUInteger)containerCount {
return [[[self responseHeaders] objectForKey:@"X-Account-Container-Count"] intValue];
}
- (NSUInteger)bytesUsed {
return [[[self responseHeaders] objectForKey:@"X-Account-Bytes-Used"] intValue];
}
#pragma mark -
#pragma mark GET - Retrieve Container List
+ (id)listRequestWithLimit:(NSUInteger)limit marker:(NSString *)marker {
NSString *queryString = @"?format=xml";
if (limit > 0) {
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
}
if (marker != nil) {
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", marker]];
}
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"GET" queryString:queryString];
return request;
}
// GET /<api version>/<account>/<container>
// Create a request to list all containers
+ (id)listRequest {
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"GET"
queryString:@"?format=xml"];
return request;
}
- (NSArray *)containers {
if (xmlParserDelegate.containerObjects) {
return xmlParserDelegate.containerObjects;
}
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
if (xmlParserDelegate == nil) {
xmlParserDelegate = [[ASICloudFilesContainerXMLParserDelegate alloc] init];
}
[parser setDelegate:xmlParserDelegate];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
return xmlParserDelegate.containerObjects;
}
#pragma mark -
#pragma mark PUT - Create Container
// PUT /<api version>/<account>/<container>
+ (id)createContainerRequest:(NSString *)containerName {
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"PUT" containerName:containerName queryString:@""];
return request;
}
#pragma mark -
#pragma mark DELETE - Delete Container
// DELETE /<api version>/<account>/<container>
+ (id)deleteContainerRequest:(NSString *)containerName {
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest storageRequestWithMethod:@"DELETE" containerName:containerName queryString:@""];
return request;
}
#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
[currentElement release];
[currentContent release];
[currentObject release];
[xmlParserDelegate release];
[super dealloc];
}
@end
... ...
//
// ASICloudFilesContainerXMLParserDelegate.h
//
// Created by Michael Mayo on 1/10/10.
//
#import "ASICloudFilesRequest.h"
@class ASICloudFilesContainer;
@interface ASICloudFilesContainerXMLParserDelegate : NSObject {
NSMutableArray *containerObjects;
// Internally used while parsing the response
NSString *currentContent;
NSString *currentElement;
ASICloudFilesContainer *currentObject;
}
@property (nonatomic, retain) NSMutableArray *containerObjects;
@property (nonatomic, retain) NSString *currentElement;
@property (nonatomic, retain) NSString *currentContent;
@property (nonatomic, retain) ASICloudFilesContainer *currentObject;
@end
... ...
//
// ASICloudFilesContainerXMLParserDelegate.m
//
// Created by Michael Mayo on 1/10/10.
//
#import "ASICloudFilesContainerXMLParserDelegate.h"
#import "ASICloudFilesContainer.h"
@implementation ASICloudFilesContainerXMLParserDelegate
@synthesize containerObjects, currentElement, currentContent, currentObject;
#pragma mark -
#pragma mark XML Parser Delegate
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
[self setCurrentElement:elementName];
if ([elementName isEqualToString:@"container"]) {
[self setCurrentObject:[ASICloudFilesContainer container]];
}
[self setCurrentContent:@""];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:@"name"]) {
[self currentObject].name = [self currentContent];
} else if ([elementName isEqualToString:@"count"]) {
[self currentObject].count = [[self currentContent] intValue];
} else if ([elementName isEqualToString:@"bytes"]) {
[self currentObject].bytes = [[self currentContent] intValue];
} else if ([elementName isEqualToString:@"cdn_enabled"]) {
[self currentObject].cdnEnabled = [[self currentObject] isEqual:@"True"];
} else if ([elementName isEqualToString:@"ttl"]) {
[self currentObject].ttl = [[self currentContent] intValue];
} else if ([elementName isEqualToString:@"cdn_url"]) {
[self currentObject].cdnURL = [self currentContent];
} else if ([elementName isEqualToString:@"log_retention"]) {
[self currentObject].logRetention = [[self currentObject] isEqual:@"True"];
} else if ([elementName isEqualToString:@"referrer_acl"]) {
[self currentObject].referrerACL = [self currentContent];
} else if ([elementName isEqualToString:@"useragent_acl"]) {
[self currentObject].useragentACL = [self currentContent];
} else if ([elementName isEqualToString:@"container"]) {
// we're done with this container. time to move on to the next
if (containerObjects == nil) {
containerObjects = [[NSMutableArray alloc] init];
}
[containerObjects addObject:currentObject];
[self setCurrentObject:nil];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
}
#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
[containerObjects release];
[currentElement release];
[currentContent release];
[currentObject release];
[super dealloc];
}
@end
... ...
//
// ASICloudFilesObject.h
//
// Created by Michael Mayo on 1/7/10.
//
#import <Foundation/Foundation.h>
@interface ASICloudFilesObject : NSObject {
NSString *name;
NSString *hash;
NSUInteger bytes;
NSString *contentType;
NSDate *lastModified;
NSData *data;
NSMutableDictionary *metadata;
}
@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSString *hash;
@property (nonatomic) NSUInteger bytes;
@property (nonatomic, retain) NSString *contentType;
@property (nonatomic, retain) NSDate *lastModified;
@property (nonatomic, retain) NSData *data;
@property (nonatomic, retain) NSMutableDictionary *metadata;
+ (id)object;
@end
... ...
//
// ASICloudFilesObject.m
//
// Created by Michael Mayo on 1/7/10.
//
#import "ASICloudFilesObject.h"
@implementation ASICloudFilesObject
@synthesize name, hash, bytes, contentType, lastModified, data, metadata;
+ (id)object {
ASICloudFilesObject *object = [[[self alloc] init] autorelease];
return object;
}
-(void)dealloc {
[name release];
[hash release];
[contentType release];
[lastModified release];
[data release];
[metadata release];
[super dealloc];
}
@end
... ...
//
// ASICloudFilesObjectRequest.h
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASICloudFilesRequest.h"
@class ASICloudFilesObject;
@interface ASICloudFilesObjectRequest : ASICloudFilesRequest {
NSString *accountName;
NSString *containerName;
// Internally used while parsing the response
NSString *currentContent;
NSString *currentElement;
ASICloudFilesObject *currentObject;
NSMutableArray *objects;
}
@property (nonatomic, retain) NSString *accountName;
@property (nonatomic, retain) NSString *containerName;
@property (nonatomic, retain) NSString *currentElement;
@property (nonatomic, retain) NSString *currentContent;
@property (nonatomic, retain) ASICloudFilesObject *currentObject;
// HEAD /<api version>/<account>/<container>
// HEAD operations against an account are performed to retrieve the number of Containers and the total bytes stored in Cloud Files for the account. This information is returned in two custom headers, X-Account-Container-Count and X-Account-Bytes-Used.
+ (id)containerInfoRequest:(NSString *)containerName;
- (NSUInteger)containerObjectCount;
- (NSUInteger)containerBytesUsed;
// HEAD /<api version>/<account>/<container>/<object>
// to get metadata
+ (id)objectInfoRequest:(NSString *)containerName objectPath:(NSString *)objectPath;
- (NSArray *)objects;
+ (id)listRequestWithContainer:(NSString *)containerName;
+ (id)listRequestWithContainer:(NSString *)containerName limit:(NSUInteger)limit marker:(NSString *)marker prefix:(NSString *)prefix path:(NSString *)path;
// Conditional GET headers: If-Match • If-None-Match • If-Modified-Since • If-Unmodified-Since
// HTTP Range header: “Range: bytes=0-5” • “Range: bytes=-5” • “Range: bytes=32-“
+ (id)getObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath;
- (ASICloudFilesObject *)object;
// PUT /<api version>/<account>/<container>/<object>
// PUT operations are used to write, or overwrite, an Object's metadata and content.
// The Object can be created with custom metadata via HTTP headers identified with the “X-Object-Meta-” prefix.
+ (id)putObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object;
+ (id)putObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath contentType:(NSString *)contentType objectData:(NSData *)objectData metadata:(NSDictionary *)metadata etag:(NSString *)etag;
// POST /<api version>/<account>/<container>/<object>
// POST operations against an Object name are used to set and overwrite arbitrary key/value metadata. You cannot use the POST operation to change any of the Object's other headers such as Content-Type, ETag, etc. It is not used to upload storage Objects (see PUT).
// A POST request will delete all existing metadata added with a previous PUT/POST.
+ (id)postObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object;
+ (id)postObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath metadata:(NSDictionary *)metadata;
// DELETE /<api version>/<account>/<container>/<object>
+ (id)deleteObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath;
@end
... ...
//
// ASICloudFilesObjectRequest.m
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASICloudFilesObjectRequest.h"
#import "ASICloudFilesObject.h"
@implementation ASICloudFilesObjectRequest
@synthesize currentElement, currentContent, currentObject;
@synthesize accountName, containerName;
#pragma mark -
#pragma mark Constructors
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName {
NSString *urlString = [NSString stringWithFormat:@"%@/%@", [ASICloudFilesRequest storageURL], containerName];
ASICloudFilesObjectRequest *request = [[ASICloudFilesObjectRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setRequestMethod:method];
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
request.containerName = containerName;
return request;
}
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName queryString:(NSString *)queryString {
NSString *urlString = [NSString stringWithFormat:@"%@/%@%@", [ASICloudFilesRequest storageURL], containerName, queryString];
ASICloudFilesObjectRequest *request = [[ASICloudFilesObjectRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setRequestMethod:method];
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
request.containerName = containerName;
return request;
}
+ (id)storageRequestWithMethod:(NSString *)method containerName:(NSString *)containerName objectPath:(NSString *)objectPath {
NSString *urlString = [NSString stringWithFormat:@"%@/%@/%@", [ASICloudFilesRequest storageURL], containerName, objectPath];
ASICloudFilesObjectRequest *request = [[ASICloudFilesObjectRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setRequestMethod:method];
[request addRequestHeader:@"X-Auth-Token" value:[ASICloudFilesRequest authToken]];
request.containerName = containerName;
return request;
}
#pragma mark -
#pragma mark HEAD - Container Info
+ (id)containerInfoRequest:(NSString *)containerName {
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"HEAD" containerName:containerName];
return request;
}
- (NSUInteger)containerObjectCount {
return [[[self responseHeaders] objectForKey:@"X-Container-Object-Count"] intValue];
}
- (NSUInteger)containerBytesUsed {
return [[[self responseHeaders] objectForKey:@"X-Container-Bytes-Used"] intValue];
}
#pragma mark -
#pragma mark HEAD - Object Info
+ (id)objectInfoRequest:(NSString *)containerName objectPath:(NSString *)objectPath {
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"HEAD" containerName:containerName objectPath:objectPath];
return request;
}
#pragma mark -
#pragma mark GET - List Objects
+ (NSString *)queryStringWithContainer:(NSString *)container limit:(NSUInteger)limit marker:(NSString *)marker prefix:(NSString *)prefix path:(NSString *)path {
NSString *queryString = @"?format=xml";
if (limit && limit > 0) {
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&limit=%i", limit]];
}
if (marker) {
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&marker=%@", [marker stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
}
if (path) {
queryString = [queryString stringByAppendingString:[NSString stringWithFormat:@"&path=%@", [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]]];
}
return queryString;
}
+ (id)listRequestWithContainer:(NSString *)containerName limit:(NSUInteger)limit marker:(NSString *)marker prefix:(NSString *)prefix path:(NSString *)path {
NSString *queryString = [ASICloudFilesObjectRequest queryStringWithContainer:containerName limit:limit marker:marker prefix:prefix path:path];
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"GET" containerName:containerName queryString:queryString];
return request;
}
+ (id)listRequestWithContainer:(NSString *)containerName {
return [ASICloudFilesObjectRequest listRequestWithContainer:containerName limit:0 marker:nil prefix:nil path:nil];
}
- (NSArray *)objects {
if (objects) {
return objects;
}
objects = [[[NSMutableArray alloc] init] autorelease];
NSXMLParser *parser = [[[NSXMLParser alloc] initWithData:[self responseData]] autorelease];
[parser setDelegate:self];
[parser setShouldProcessNamespaces:NO];
[parser setShouldReportNamespacePrefixes:NO];
[parser setShouldResolveExternalEntities:NO];
[parser parse];
return objects;
}
#pragma mark -
#pragma mark GET - Retrieve Object
+ (id)getObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath {
return [ASICloudFilesObjectRequest storageRequestWithMethod:@"GET" containerName:containerName objectPath:objectPath];
}
- (ASICloudFilesObject *)object {
ASICloudFilesObject *object = [ASICloudFilesObject object];
NSString *path = [self url].path;
NSRange range = [path rangeOfString:self.containerName];
path = [path substringFromIndex:range.location + range.length + 1];
object.name = path;
object.hash = [[self responseHeaders] objectForKey:@"ETag"];
object.bytes = [[[self responseHeaders] objectForKey:@"Content-Length"] intValue];
object.contentType = [[self responseHeaders] objectForKey:@"Content-Type"];
object.lastModified = [[self responseHeaders] objectForKey:@"Last-Modified"];
object.metadata = [[NSMutableDictionary alloc] init];
NSDictionary *headers = [self responseHeaders];
NSArray *keys = [headers allKeys];
for (int i = 0; i < [keys count]; i++) {
NSString *key = [keys objectAtIndex:i];
NSString *value = [headers objectForKey:key];
NSRange range = [key rangeOfString:@"X-Object-Meta-"];
if (range.location == 0) {
[object.metadata setObject:value forKey:[key substringFromIndex:range.length]];
}
}
object.data = [self responseData];
return object;
}
#pragma mark -
#pragma mark PUT - Upload Object
+ (id)putObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object {
return [self putObjectRequestWithContainer:containerName objectPath:object.name contentType:object.contentType objectData:object.data metadata:object.metadata etag:nil];
}
+ (id)putObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath contentType:(NSString *)contentType objectData:(NSData *)objectData metadata:(NSDictionary *)metadata etag:(NSString *)etag {
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"PUT" containerName:containerName objectPath:objectPath];
[request addRequestHeader:@"Content-Type" value:contentType];
[request addRequestHeader:@"Content-Length" value:[NSString stringWithFormat:@"%i", objectData.length]];
// add metadata to headers
if (metadata) {
NSArray *keys = [metadata allKeys];
for (int i = 0; i < [keys count]; i++) {
NSString *key = [keys objectAtIndex:i];
NSString *value = [metadata objectForKey:key];
[request addRequestHeader:[NSString stringWithFormat:@"X-Object-Meta-%@", key] value:value];
}
}
[request appendPostData:objectData];
return request;
}
#pragma mark -
#pragma mark POST - Set Object Metadata
+ (id)postObjectRequestWithContainer:(NSString *)containerName object:(ASICloudFilesObject *)object {
return [self postObjectRequestWithContainer:containerName objectPath:object.name metadata:object.metadata];
}
+ (id)postObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath metadata:(NSDictionary *)metadata {
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"POST" containerName:containerName objectPath:objectPath];
// add metadata to headers
if (metadata) {
NSArray *keys = [metadata allKeys];
for (int i = 0; i < [keys count]; i++) {
NSString *key = [keys objectAtIndex:i];
NSString *value = [metadata objectForKey:key];
[request addRequestHeader:[NSString stringWithFormat:@"X-Object-Meta-%@", key] value:value];
}
}
return request;
}
#pragma mark -
#pragma mark DELETE - Delete Object
+ (id)deleteObjectRequestWithContainer:(NSString *)containerName objectPath:(NSString *)objectPath {
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest storageRequestWithMethod:@"DELETE" containerName:containerName objectPath:objectPath];
return request;
}
#pragma mark -
#pragma mark XML Parser Delegate
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
[self setCurrentElement:elementName];
if ([elementName isEqualToString:@"object"]) {
[self setCurrentObject:[ASICloudFilesObject object]];
}
[self setCurrentContent:@""];
}
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
if ([elementName isEqualToString:@"name"]) {
[self currentObject].name = [self currentContent];
} else if ([elementName isEqualToString:@"hash"]) {
[self currentObject].hash = [self currentContent];
} else if ([elementName isEqualToString:@"bytes"]) {
[self currentObject].bytes = [[self currentContent] intValue];
} else if ([elementName isEqualToString:@"content_type"]) {
[self currentObject].contentType = [self currentContent];
} else if ([elementName isEqualToString:@"last_modified"]) {
[self currentObject].lastModified = [self dateFromString:[self currentContent]];
} else if ([elementName isEqualToString:@"object"]) {
// we're done with this object. time to move on to the next
[objects addObject:currentObject];
[self setCurrentObject:nil];
}
}
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
[self setCurrentContent:[[self currentContent] stringByAppendingString:string]];
}
#pragma mark -
#pragma mark Memory Management
- (void)dealloc {
[currentElement release];
[currentContent release];
[currentObject release];
[accountName release];
[containerName release];
[super dealloc];
}
@end
... ...
//
// ASICloudFilesRequest.h
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Michael Mayo on 22/12/09.
// mike.mayo@rackspace.com or mike@overhrd.com
// twitter.com/greenisus
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
// A class for accessing data stored on the Rackspace Cloud Files Service
// http://www.rackspacecloud.com/cloud_hosting_products/files
//
// Cloud Files Developer Guide:
// http://docs.rackspacecloud.com/servers/api/cs-devguide-latest.pdf
#import <Foundation/Foundation.h>
#import "ASIHTTPRequest.h"
@interface ASICloudFilesRequest : ASIHTTPRequest {
}
+ (NSString *)storageURL;
+ (NSString *)cdnManagementURL;
+ (NSString *)authToken;
#pragma mark Rackspace Cloud Authentication
+ (id)authenticationRequest;
+ (void)authenticate;
+ (NSString *)username;
+ (void)setUsername:(NSString *)username;
+ (NSString *)apiKey;
+ (void)setApiKey:(NSString *)apiKey;
// helper to parse dates in the format returned by Cloud Files
-(NSDate *)dateFromString:(NSString *)dateString;
@end
... ...
//
// ASICloudFilesRequest.m
// Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
//
// Created by Michael Mayo on 22/12/09.
// Copyright 2009 All-Seeing Interactive. All rights reserved.
//
// A class for accessing data stored on Rackspace's Cloud Files Service
// http://www.rackspacecloud.com/cloud_hosting_products/files
//
// Cloud Files Developer Guide:
// http://docs.rackspacecloud.com/servers/api/cs-devguide-latest.pdf
#import "ASICloudFilesRequest.h"
static NSString *username = nil;
static NSString *apiKey = nil;
static NSString *authToken = nil;
static NSString *storageURL = nil;
static NSString *cdnManagementURL = nil;
static NSString *rackspaceCloudAuthURL = @"https://auth.api.rackspacecloud.com/v1.0";
@implementation ASICloudFilesRequest
#pragma mark -
#pragma mark Attributes and Service URLs
+ (NSString *)authToken {
return authToken;
}
+ (NSString *)storageURL {
return storageURL;
}
+ (NSString *)cdnManagementURL {
return cdnManagementURL;
}
#pragma mark -
#pragma mark Authentication
+ (id)authenticationRequest {
ASIHTTPRequest *request = [[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:rackspaceCloudAuthURL]];
[request addRequestHeader:@"X-Auth-User" value:username];
[request addRequestHeader:@"X-Auth-Key" value:apiKey];
return request;
}
+ (void)authenticate {
ASIHTTPRequest *request = [ASICloudFilesRequest authenticationRequest];
[request start];
if (![request error]) {
NSDictionary *responseHeaders = [request responseHeaders];
authToken = [responseHeaders objectForKey:@"X-Auth-Token"];
storageURL = [responseHeaders objectForKey:@"X-Storage-Url"];
cdnManagementURL = [responseHeaders objectForKey:@"X-Cdn-Management-Url"];
}
}
+ (NSString *)username {
return username;
}
+ (void)setUsername:(NSString *)newUsername {
[username release];
username = [newUsername retain];
}
+ (NSString *)apiKey {
return apiKey;
}
+ (void)setApiKey:(NSString *)newApiKey {
[apiKey release];
apiKey = [newApiKey retain];
}
#pragma mark -
#pragma mark Date Parser
-(NSDate *)dateFromString:(NSString *)dateString {
NSDateFormatter *format = [[NSDateFormatter alloc] init];
// example: 2009-11-04T19:46:20.192723
[format setDateFormat:@"yyyy-MM-dd'T'H:mm:ss"];
NSDate *date = [format dateFromString:dateString];
[format release];
return date;
}
@end
... ...
//
// ASICloudFilesRequestTests.h
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASITestCase.h"
@class ASINetworkQueue;
@interface ASICloudFilesRequestTests : ASITestCase {
ASINetworkQueue *networkQueue;
float progress;
}
@property (retain,nonatomic) ASINetworkQueue *networkQueue;
@end
... ...
//
// ASICloudFilesRequestTests.m
//
// Created by Michael Mayo on 1/6/10.
//
#import "ASICloudFilesRequestTests.h"
// models
#import "ASICloudFilesContainer.h"
#import "ASICloudFilesObject.h"
// requests
#import "ASICloudFilesRequest.h"
#import "ASICloudFilesContainerRequest.h"
#import "ASICloudFilesObjectRequest.h"
#import "ASICloudFilesCDNRequest.h"
// Fill in these to run the tests that actually connect and manipulate objects on Cloud Files
static NSString *username = @"";
static NSString *apiKey = @"";
@implementation ASICloudFilesRequestTests
@synthesize networkQueue;
// Authenticate before any test if there's no auth token present
- (void)authenticate {
if (![ASICloudFilesRequest authToken]) {
[ASICloudFilesRequest setUsername:username];
[ASICloudFilesRequest setApiKey:apiKey];
[ASICloudFilesRequest authenticate];
}
}
// ASICloudFilesRequest
- (void)testAuthentication {
[self authenticate];
GHAssertNotNil([ASICloudFilesRequest authToken], @"Failed to authenticate and obtain authentication token");
GHAssertNotNil([ASICloudFilesRequest storageURL], @"Failed to authenticate and obtain storage URL");
GHAssertNotNil([ASICloudFilesRequest cdnManagementURL], @"Failed to authenticate and obtain CDN URL");
}
- (void)testDateParser {
ASICloudFilesRequest *request = [[ASICloudFilesRequest alloc] init];
NSDate *date = [request dateFromString:@"2009-11-04T19:46:20.192723"];
GHAssertNotNil(date, @"Failed to parse date string");
date = [request dateFromString:@"invalid date string"];
GHAssertNil(date, @"Failed to not parse with invalid date string");
[request release];
}
// ASICloudFilesContainerRequest
- (void)testAccountInfo {
[self authenticate];
ASICloudFilesContainerRequest *request = [ASICloudFilesContainerRequest accountInfoRequest];
[request start];
GHAssertTrue([request containerCount] > 0, @"Failed to retrieve account info");
GHAssertTrue([request bytesUsed] > 0, @"Failed to retrieve account info");
}
- (void)testContainerList {
[self authenticate];
NSArray *containers = nil;
ASICloudFilesContainerRequest *containerListRequest = [ASICloudFilesContainerRequest listRequest];
[containerListRequest start];
containers = [containerListRequest containers];
GHAssertTrue([containers count] > 0, @"Failed to list containers");
for (int i = 0; i < [containers count]; i++) {
ASICloudFilesContainer *container = [containers objectAtIndex:i];
GHAssertNotNil(container.name, @"Failed to parse container");
}
ASICloudFilesContainerRequest *limitContainerListRequest = [ASICloudFilesContainerRequest listRequestWithLimit:2 marker:nil];
[limitContainerListRequest start];
containers = [limitContainerListRequest containers];
GHAssertTrue([containers count] == 2, @"Failed to limit container list");
}
- (void)testContainerCreate {
[self authenticate];
ASICloudFilesContainerRequest *createContainerRequest = [ASICloudFilesContainerRequest createContainerRequest:@"ASICloudFilesContainerTest"];
[createContainerRequest start];
GHAssertTrue([createContainerRequest error] == nil, @"Failed to create container");
}
- (void)testContainerDelete {
[self authenticate];
ASICloudFilesContainerRequest *deleteContainerRequest = [ASICloudFilesContainerRequest deleteContainerRequest:@"ASICloudFilesContainerTest"];
[deleteContainerRequest start];
GHAssertTrue([deleteContainerRequest error] == nil, @"Failed to delete container");
}
// ASICloudFilesObjectRequest
- (void)testContainerInfo {
[self authenticate];
// create a file first
ASICloudFilesContainerRequest *createContainerRequest = [ASICloudFilesContainerRequest createContainerRequest:@"ASICloudFilesTest"];
[createContainerRequest start];
NSData *data = [@"this is a test" dataUsingEncoding:NSUTF8StringEncoding];
ASICloudFilesObjectRequest *putRequest
= [ASICloudFilesObjectRequest putObjectRequestWithContainer:@"ASICloudFilesTest"
objectPath:@"infotestfile.txt" contentType:@"text/plain"
objectData:data metadata:nil etag:nil];
[putRequest start];
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest containerInfoRequest:@"ASICloudFilesTest"];
[request start];
GHAssertTrue([request containerObjectCount] > 0, @"Failed to retrieve container info");
GHAssertTrue([request containerBytesUsed] > 0, @"Failed to retrieve container info");
}
- (void)testObjectInfo {
[self authenticate];
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest objectInfoRequest:@"ASICloudFilesTest" objectPath:@"infotestfile.txt"];
[request start];
ASICloudFilesObject *object = [request object];
GHAssertNotNil(object, @"Failed to retrieve object");
GHAssertTrue([object.metadata count] > 0, @"Failed to parse metadata");
GHAssertTrue([object.metadata objectForKey:@"Test"] != nil, @"Failed to parse metadata");
}
- (void)testObjectList {
[self authenticate];
ASICloudFilesObjectRequest *objectListRequest = [ASICloudFilesObjectRequest listRequestWithContainer:@"ASICloudFilesTest"];
[objectListRequest start];
NSArray *containers = [objectListRequest objects];
GHAssertTrue([containers count] > 0, @"Failed to list objects");
for (int i = 0; i < [containers count]; i++) {
ASICloudFilesObject *object = [containers objectAtIndex:i];
GHAssertNotNil(object.name, @"Failed to parse object");
}
}
- (void)testGetObject {
[self authenticate];
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest getObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"infotestfile.txt"];
[request start];
ASICloudFilesObject *object = [request object];
GHAssertNotNil(object, @"Failed to retrieve object");
GHAssertNotNil(object.name, @"Failed to parse object name");
GHAssertTrue(object.bytes > 0, @"Failed to parse object bytes");
GHAssertNotNil(object.contentType, @"Failed to parse object content type");
GHAssertNotNil(object.lastModified, @"Failed to parse object last modified");
GHAssertNotNil(object.data, @"Failed to parse object data");
}
- (void)testPutObject {
[self authenticate];
ASICloudFilesContainerRequest *createContainerRequest
= [ASICloudFilesContainerRequest createContainerRequest:@"ASICloudFilesTest"];
[createContainerRequest start];
NSData *data = [@"this is a test" dataUsingEncoding:NSUTF8StringEncoding];
ASICloudFilesObjectRequest *putRequest
= [ASICloudFilesObjectRequest putObjectRequestWithContainer:@"ASICloudFilesTest"
objectPath:@"puttestfile.txt" contentType:@"text/plain"
objectData:data metadata:nil etag:nil];
[putRequest start];
GHAssertNil([putRequest error], @"Failed to PUT object");
ASICloudFilesObjectRequest *getRequest = [ASICloudFilesObjectRequest getObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"puttestfile.txt"];
[getRequest start];
ASICloudFilesObject *object = [getRequest object];
NSString *string = [[NSString alloc] initWithData:object.data encoding:NSASCIIStringEncoding];
GHAssertNotNil(object, @"Failed to retrieve new object");
GHAssertNotNil(object.name, @"Failed to parse object name");
GHAssertEqualStrings(object.name, @"puttestfile.txt", @"Failed to parse object name", @"Failed to parse object name");
GHAssertNotNil(object.data, @"Failed to parse object data");
GHAssertEqualStrings(string, @"this is a test", @"Failed to parse object data", @"Failed to parse object data");
[string release];
ASICloudFilesContainerRequest *deleteContainerRequest = [ASICloudFilesContainerRequest deleteContainerRequest:@"ASICloudFilesTest"];
[deleteContainerRequest start];
}
- (void)testPostObject {
[self authenticate];
NSMutableDictionary *metadata = [[NSMutableDictionary alloc] initWithCapacity:2];
[metadata setObject:@"test" forKey:@"Test"];
[metadata setObject:@"test" forKey:@"ASITest"];
ASICloudFilesObject *object = [ASICloudFilesObject object];
object.name = @"infotestfile.txt";
object.metadata = metadata;
ASICloudFilesObjectRequest *request = [ASICloudFilesObjectRequest postObjectRequestWithContainer:@"ASICloudFilesTest" object:object];
[request start];
GHAssertTrue([request responseStatusCode] == 202, @"Failed to post object metadata");
[metadata release];
}
- (void)testDeleteObject {
[self authenticate];
ASICloudFilesObjectRequest *deleteRequest = [ASICloudFilesObjectRequest deleteObjectRequestWithContainer:@"ASICloudFilesTest" objectPath:@"puttestfile.txt"];
[deleteRequest start];
GHAssertTrue([deleteRequest responseStatusCode] == 204, @"Failed to delete object");
}
#pragma mark -
#pragma mark CDN Tests
- (void)testCDNContainerInfo {
[self authenticate];
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest containerInfoRequest:@"ASICloudFilesTest"];
[request start];
GHAssertTrue([request responseStatusCode] == 204, @"Failed to retrieve CDN container info");
GHAssertTrue([request cdnEnabled], @"Failed to retrieve CDN container info");
GHAssertNotNil([request cdnURI], @"Failed to retrieve CDN container info");
GHAssertTrue([request cdnTTL] > 0, @"Failed to retrieve CDN container info");
}
- (void)testCDNContainerList {
[self authenticate];
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest listRequest];
[request start];
GHAssertNotNil([request containers], @"Failed to retrieve CDN container list");
}
- (void)testCDNContainerListWithParams {
[self authenticate];
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest listRequestWithLimit:2 marker:nil enabledOnly:YES];
[request start];
GHAssertNotNil([request containers], @"Failed to retrieve CDN container list");
GHAssertTrue([[request containers] count] == 2, @"Failed to retrieve limited CDN container list");
}
- (void)testCDNPut {
[self authenticate];
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest putRequestWithContainer:@"ASICloudFilesTest"];
[request start];
GHAssertNotNil([request cdnURI], @"Failed to PUT to CDN container");
}
- (void)testCDNPost {
[self authenticate];
ASICloudFilesCDNRequest *request = [ASICloudFilesCDNRequest postRequestWithContainer:@"ASICloudFilesTest" cdnEnabled:YES ttl:86600];
[request start];
GHAssertNotNil([request cdnURI], @"Failed to POST to CDN container");
}
#pragma mark -
#pragma mark Memory Management
-(void)dealloc {
[networkQueue release];
[super dealloc];
}
@end
... ...
... ... @@ -23,9 +23,7 @@
[super dealloc];
}
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Add the tab bar controller's current view as a subview of the window
[window addSubview:[tabBarController view]];
[[tabBarController view] setFrame:CGRectMake(0,47,320,433)];
... ...