Ben Copsey

More work on download cache

Added cache example to Mac project
... ... @@ -26,7 +26,7 @@ typedef enum _ASICacheStoragePolicy {
@protocol ASICacheDelegate <NSObject>
@required
- (BOOL)useDataFromCacheForRequest:(ASIHTTPRequest *)request;
- (ASICachePolicy)defaultCachePolicy;
- (void)storeResponseForRequest:(ASIHTTPRequest *)request;
- (NSDictionary *)cachedHeadersForRequest:(ASIHTTPRequest *)request;
- (NSData *)cachedResponseDataForRequest:(ASIHTTPRequest *)request;
... ...
... ... @@ -13,7 +13,7 @@
ASICachePolicy defaultCachePolicy;
ASICacheStoragePolicy defaultCacheStoragePolicy;
NSString *storagePath;
NSLock *accessLock;
NSRecursiveLock *accessLock;
BOOL shouldRespectCacheHeaders;
}
+ (id)sharedCache;
... ... @@ -22,6 +22,6 @@
@property (assign) ASICachePolicy defaultCachePolicy;
@property (assign) ASICacheStoragePolicy defaultCacheStoragePolicy;
@property (retain) NSString *storagePath;
@property (retain) NSLock *accessLock;
@property (retain) NSRecursiveLock *accessLock;
@property (assign) BOOL shouldRespectCacheHeaders;
@end
... ...
... ... @@ -27,7 +27,7 @@ static NSString *permanentCacheFolder = @"PermanentStore";
self = [super init];
[self setDefaultCachePolicy:ASIReloadIfDifferentCachePolicy];
[self setDefaultCacheStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
[self setAccessLock:[[[NSLock alloc] init] autorelease]];
[self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]];
return self;
}
... ... @@ -51,21 +51,23 @@ static NSString *permanentCacheFolder = @"PermanentStore";
- (void)setStoragePath:(NSString *)path
{
[[self accessLock] lock];
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
[storagePath release];
storagePath = [path retain];
BOOL isDirectory = NO;
NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil];
for (NSString *directory in directories) {
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:directory isDirectory:&isDirectory];
if (exists && !isDirectory) {
[NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",path];
[NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory];
} else if (!exists) {
[[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil];
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
[NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",path];
[[NSFileManager defaultManager] createDirectoryAtPath:directory attributes:nil];
if (![[NSFileManager defaultManager] fileExistsAtPath:directory]) {
[NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory];
}
}
}
[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
[[self accessLock] unlock];
}
... ... @@ -103,10 +105,14 @@ static NSString *permanentCacheFolder = @"PermanentStore";
NSString *metadataPath = [path stringByAppendingPathExtension:@"cachedheaders"];
NSString *dataPath = [path stringByAppendingPathExtension:@"cacheddata"];
[[request responseHeaders] writeToFile:metadataPath atomically:NO];
NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
if ([request isResponseCompressed]) {
[responseHeaders removeObjectForKey:@"Content-Encoding"];
}
[responseHeaders writeToFile:metadataPath atomically:NO];
if ([request responseData]) {
[[request responseData] writeToFile:path atomically:NO];
[[request responseData] writeToFile:dataPath atomically:NO];
} else if ([request downloadDestinationPath]) {
[[NSFileManager defaultManager] copyPath:[request downloadDestinationPath] toPath:dataPath handler:nil];
}
... ... @@ -185,27 +191,6 @@ static NSString *permanentCacheFolder = @"PermanentStore";
return YES;
}
- (BOOL)useDataFromCacheForRequest:(ASIHTTPRequest *)request
{
if (![self storagePath]) {
return NO;
}
NSDictionary *headers = [self cachedHeadersForRequest:request];
if (!headers) {
return NO;
}
NSString *dataPath = [self pathToCachedResponseDataForRequest:request];
if (!dataPath) {
return NO;
}
[request setResponseHeaders:headers];
if ([request downloadDestinationPath]) {
[request setDownloadDestinationPath:dataPath];
} else {
[request setRawResponseData:[NSMutableData dataWithData:[self cachedResponseDataForRequest:request]]];
}
return YES;
}
- (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy
{
... ...
... ... @@ -23,7 +23,7 @@
// Automatically set on build
NSString *ASIHTTPRequestVersion = @"v1.6.2-9 2010-05-02";
NSString *ASIHTTPRequestVersion = @"v1.6.2-10 2010-05-02";
NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
... ... @@ -145,6 +145,8 @@ static id <ASICacheDelegate> defaultCache = nil;
- (void)updateStatus:(NSTimer*)timer;
- (BOOL)useDataFromCache;
#if TARGET_OS_IPHONE
+ (void)registerForNetworkReachabilityNotifications;
+ (void)unsubscribeFromNetworkReachabilityNotifications;
... ... @@ -798,15 +800,20 @@ static id <ASICacheDelegate> defaultCache = nil;
return;
}
// See if we should pull from the cache rather than fetching the data
if ([self downloadCache] && [self cachePolicy] == ASIOnlyLoadIfNotCachedCachePolicy) {
if ([[self downloadCache] useDataFromCacheForRequest:self]) {
[[self cancelledLock] unlock];
return;
if ([self downloadCache]) {
if ([self cachePolicy] == ASIDefaultCachePolicy) {
[self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
}
// See if we should pull from the cache rather than fetching the data
if ([self cachePolicy] == ASIOnlyLoadIfNotCachedCachePolicy) {
if ([self useDataFromCache]) {
[[self cancelledLock] unlock];
return;
}
}
}
[self requestStarted];
[self setDownloadComplete:NO];
... ... @@ -1573,11 +1580,7 @@ static id <ASICacheDelegate> defaultCache = nil;
}
if ([self downloadCache] && [self cachePolicy] == ASIUseCacheIfLoadFailsCachePolicy) {
if ([[self downloadCache] useDataFromCacheForRequest:self]) {
[self markAsFinished];
if ([self mainRequest]) {
[[self mainRequest] markAsFinished];
}
if ([self useDataFromCache]) {
return;
}
}
... ... @@ -1638,10 +1641,8 @@ static id <ASICacheDelegate> defaultCache = nil;
CFRelease(headerFields);
if ([self downloadCache] && [self cachePolicy] == ASIReloadIfDifferentCachePolicy) {
if ([[self downloadCache] useDataFromCacheForRequest:self]) {
if ([self useDataFromCache]) {
CFRelease(message);
[self cancelLoad];
[self markAsFinished];
return;
}
}
... ... @@ -2662,6 +2663,45 @@ static id <ASICacheDelegate> defaultCache = nil;
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (BOOL)useDataFromCache
{
NSDictionary *headers = [[self downloadCache] cachedHeadersForRequest:self];
if (!headers) {
return NO;
}
NSString *dataPath = [[self downloadCache] pathToCachedResponseDataForRequest:self];
if (!dataPath) {
return NO;
}
[self cancelLoad];
ASIHTTPRequest *theRequest = self;
if ([self mainRequest]) {
theRequest = [self mainRequest];
}
[theRequest setResponseHeaders:headers];
if ([theRequest downloadDestinationPath]) {
[theRequest setDownloadDestinationPath:dataPath];
} else {
[theRequest setRawResponseData:[NSMutableData dataWithData:[[self downloadCache] cachedResponseDataForRequest:self]]];
}
[theRequest setContentLength:[[[self responseHeaders] objectForKey:@"Content-Length"] longLongValue]];
[theRequest setTotalBytesRead:[self contentLength]];
[theRequest setComplete:YES];
[theRequest setDownloadComplete:YES];
[theRequest updateProgressIndicators];
[theRequest requestFinished];
[theRequest markAsFinished];
if ([self mainRequest]) {
[self markAsFinished];
}
return YES;
}
- (BOOL)retryUsingNewConnection
{
if ([self retryCount] == 0) {
... ...
... ... @@ -40,6 +40,11 @@
ASIHTTPRequest *bigFetchRequest;
IBOutlet NSTextField *postStatus;
IBOutlet NSTableView *tableView;
IBOutlet NSTextField *tableLoadStatus;
NSMutableArray *rowData;
ASINetworkQueue *tableQueue;
}
- (IBAction)simpleURLFetch:(id)sender;
... ... @@ -57,6 +62,10 @@
- (IBAction)throttleBandwidth:(id)sender;
- (IBAction)reloadTableData:(id)sender;
- (IBAction)clearCache:(id)sender;
@property (retain, nonatomic) ASIHTTPRequest *bigFetchRequest;
@property (retain, nonatomic) NSMutableArray *rowData;
@property (retain, nonatomic) ASINetworkQueue *tableQueue;
@end
... ...
... ... @@ -9,6 +9,7 @@
#import "ASIHTTPRequest.h"
#import "ASIFormDataRequest.h"
#import "ASINetworkQueue.h"
#import "ASIDownloadCache.h"
@interface AppDelegate ()
- (void)updateBandwidthUsageIndicator;
... ... @@ -25,6 +26,7 @@
@implementation AppDelegate
- (id)init
{
[super init];
... ... @@ -307,6 +309,82 @@
[postStatus setStringValue:[NSString stringWithFormat:@"Post Failed: %@",[[request error] localizedDescription]]];
}
- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
{
if ([[tabViewItem label] isEqualToString:@"Cache"]) {
[self reloadTableData:nil];
}
}
- (IBAction)reloadTableData:(id)sender
{
[[self tableQueue] cancelAllOperations];
[self setTableQueue:[ASINetworkQueue queue]];
[[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/table-row-data.xml"]];
[request setDownloadCache:[ASIDownloadCache sharedCache]];
[request setDidFinishSelector:@selector(tableViewDataFetchFinished:)];
[request setDelegate:self];
[[self tableQueue] addOperation:request];
[[self tableQueue] go];
}
- (void)tableViewDataFetchFailed:(ASIHTTPRequest *)request
{
if ([[request error] domain] != NetworkRequestErrorDomain || ![[request error] code] == ASIRequestCancelledErrorType) {
[tableLoadStatus setStringValue:@"Loading data failed"];
}
}
- (void)tableViewDataFetchFinished:(ASIHTTPRequest *)request
{
[self setRowData:[NSMutableArray array]];
NSXMLDocument *xml = [[[NSXMLDocument alloc] initWithData:[request responseData] options:NSXMLDocumentValidate error:nil] autorelease];
for (NSXMLElement *row in [[xml rootElement] elementsForName:@"row"]) {
NSMutableDictionary *rowInfo = [NSMutableDictionary dictionary];
NSString *description = [[[row elementsForName:@"description"] objectAtIndex:0] stringValue];
[rowInfo setValue:description forKey:@"description"];
NSString *imageURL = [[[row elementsForName:@"image"] objectAtIndex:0] stringValue];
ASIHTTPRequest *imageRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageURL]];
[imageRequest setDownloadCache:[ASIDownloadCache sharedCache]];
[imageRequest setDidFinishSelector:@selector(rowImageDownloadFinished:)];
[imageRequest setDidFailSelector:@selector(tableViewDataFetchFailed:)];
[imageRequest setDelegate:self];
[imageRequest setUserInfo:rowInfo];
[[self tableQueue] addOperation:imageRequest];
[[self rowData] addObject:rowInfo];
}
[tableView reloadData];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
return [[self rowData] count];
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
if ([[aTableColumn identifier] isEqualToString:@"image"]) {
return [[[self rowData] objectAtIndex:rowIndex] objectForKey:@"image"];
} else {
return [[[self rowData] objectAtIndex:rowIndex] objectForKey:@"description"];
}
}
- (void)rowImageDownloadFinished:(ASIHTTPRequest *)request
{
[(NSMutableDictionary *)[request userInfo] setObject:[[[NSImage alloc] initWithData:[request responseData]] autorelease] forKey:@"image"];
[tableView reloadData]; // Not efficient, but I hate table view programming :)
}
- (IBAction)clearCache:(id)sender
{
[[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
}
@synthesize bigFetchRequest;
@synthesize rowData;
@synthesize tableQueue;
@end
... ...
This diff is collapsed. Click to expand it.