Ben Copsey

More work on download cache

Added cache example to Mac project
@@ -26,7 +26,7 @@ typedef enum _ASICacheStoragePolicy { @@ -26,7 +26,7 @@ typedef enum _ASICacheStoragePolicy {
26 @protocol ASICacheDelegate <NSObject> 26 @protocol ASICacheDelegate <NSObject>
27 27
28 @required 28 @required
29 -- (BOOL)useDataFromCacheForRequest:(ASIHTTPRequest *)request; 29 +- (ASICachePolicy)defaultCachePolicy;
30 - (void)storeResponseForRequest:(ASIHTTPRequest *)request; 30 - (void)storeResponseForRequest:(ASIHTTPRequest *)request;
31 - (NSDictionary *)cachedHeadersForRequest:(ASIHTTPRequest *)request; 31 - (NSDictionary *)cachedHeadersForRequest:(ASIHTTPRequest *)request;
32 - (NSData *)cachedResponseDataForRequest:(ASIHTTPRequest *)request; 32 - (NSData *)cachedResponseDataForRequest:(ASIHTTPRequest *)request;
@@ -13,7 +13,7 @@ @@ -13,7 +13,7 @@
13 ASICachePolicy defaultCachePolicy; 13 ASICachePolicy defaultCachePolicy;
14 ASICacheStoragePolicy defaultCacheStoragePolicy; 14 ASICacheStoragePolicy defaultCacheStoragePolicy;
15 NSString *storagePath; 15 NSString *storagePath;
16 - NSLock *accessLock; 16 + NSRecursiveLock *accessLock;
17 BOOL shouldRespectCacheHeaders; 17 BOOL shouldRespectCacheHeaders;
18 } 18 }
19 + (id)sharedCache; 19 + (id)sharedCache;
@@ -22,6 +22,6 @@ @@ -22,6 +22,6 @@
22 @property (assign) ASICachePolicy defaultCachePolicy; 22 @property (assign) ASICachePolicy defaultCachePolicy;
23 @property (assign) ASICacheStoragePolicy defaultCacheStoragePolicy; 23 @property (assign) ASICacheStoragePolicy defaultCacheStoragePolicy;
24 @property (retain) NSString *storagePath; 24 @property (retain) NSString *storagePath;
25 -@property (retain) NSLock *accessLock; 25 +@property (retain) NSRecursiveLock *accessLock;
26 @property (assign) BOOL shouldRespectCacheHeaders; 26 @property (assign) BOOL shouldRespectCacheHeaders;
27 @end 27 @end
@@ -27,7 +27,7 @@ static NSString *permanentCacheFolder = @"PermanentStore"; @@ -27,7 +27,7 @@ static NSString *permanentCacheFolder = @"PermanentStore";
27 self = [super init]; 27 self = [super init];
28 [self setDefaultCachePolicy:ASIReloadIfDifferentCachePolicy]; 28 [self setDefaultCachePolicy:ASIReloadIfDifferentCachePolicy];
29 [self setDefaultCacheStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy]; 29 [self setDefaultCacheStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
30 - [self setAccessLock:[[[NSLock alloc] init] autorelease]]; 30 + [self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]];
31 return self; 31 return self;
32 } 32 }
33 33
@@ -51,21 +51,23 @@ static NSString *permanentCacheFolder = @"PermanentStore"; @@ -51,21 +51,23 @@ static NSString *permanentCacheFolder = @"PermanentStore";
51 - (void)setStoragePath:(NSString *)path 51 - (void)setStoragePath:(NSString *)path
52 { 52 {
53 [[self accessLock] lock]; 53 [[self accessLock] lock];
  54 + [self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
54 [storagePath release]; 55 [storagePath release];
55 storagePath = [path retain]; 56 storagePath = [path retain];
56 BOOL isDirectory = NO; 57 BOOL isDirectory = NO;
57 NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil]; 58 NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil];
58 for (NSString *directory in directories) { 59 for (NSString *directory in directories) {
59 - BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]; 60 + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:directory isDirectory:&isDirectory];
60 if (exists && !isDirectory) { 61 if (exists && !isDirectory) {
61 - [NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",path]; 62 + [NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory];
62 } else if (!exists) { 63 } else if (!exists) {
63 - [[NSFileManager defaultManager] createDirectoryAtPath:path attributes:nil]; 64 + [[NSFileManager defaultManager] createDirectoryAtPath:directory attributes:nil];
64 - if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { 65 + if (![[NSFileManager defaultManager] fileExistsAtPath:directory]) {
65 - [NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",path]; 66 + [NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory];
66 } 67 }
67 } 68 }
68 } 69 }
  70 + [self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
69 [[self accessLock] unlock]; 71 [[self accessLock] unlock];
70 } 72 }
71 73
@@ -103,10 +105,14 @@ static NSString *permanentCacheFolder = @"PermanentStore"; @@ -103,10 +105,14 @@ static NSString *permanentCacheFolder = @"PermanentStore";
103 NSString *metadataPath = [path stringByAppendingPathExtension:@"cachedheaders"]; 105 NSString *metadataPath = [path stringByAppendingPathExtension:@"cachedheaders"];
104 NSString *dataPath = [path stringByAppendingPathExtension:@"cacheddata"]; 106 NSString *dataPath = [path stringByAppendingPathExtension:@"cacheddata"];
105 107
106 - [[request responseHeaders] writeToFile:metadataPath atomically:NO]; 108 + NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
  109 + if ([request isResponseCompressed]) {
  110 + [responseHeaders removeObjectForKey:@"Content-Encoding"];
  111 + }
  112 + [responseHeaders writeToFile:metadataPath atomically:NO];
107 113
108 if ([request responseData]) { 114 if ([request responseData]) {
109 - [[request responseData] writeToFile:path atomically:NO]; 115 + [[request responseData] writeToFile:dataPath atomically:NO];
110 } else if ([request downloadDestinationPath]) { 116 } else if ([request downloadDestinationPath]) {
111 [[NSFileManager defaultManager] copyPath:[request downloadDestinationPath] toPath:dataPath handler:nil]; 117 [[NSFileManager defaultManager] copyPath:[request downloadDestinationPath] toPath:dataPath handler:nil];
112 } 118 }
@@ -185,27 +191,6 @@ static NSString *permanentCacheFolder = @"PermanentStore"; @@ -185,27 +191,6 @@ static NSString *permanentCacheFolder = @"PermanentStore";
185 return YES; 191 return YES;
186 } 192 }
187 193
188 -- (BOOL)useDataFromCacheForRequest:(ASIHTTPRequest *)request  
189 -{  
190 - if (![self storagePath]) {  
191 - return NO;  
192 - }  
193 - NSDictionary *headers = [self cachedHeadersForRequest:request];  
194 - if (!headers) {  
195 - return NO;  
196 - }  
197 - NSString *dataPath = [self pathToCachedResponseDataForRequest:request];  
198 - if (!dataPath) {  
199 - return NO;  
200 - }  
201 - [request setResponseHeaders:headers];  
202 - if ([request downloadDestinationPath]) {  
203 - [request setDownloadDestinationPath:dataPath];  
204 - } else {  
205 - [request setRawResponseData:[NSMutableData dataWithData:[self cachedResponseDataForRequest:request]]];  
206 - }  
207 - return YES;  
208 -}  
209 194
210 - (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy 195 - (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy
211 { 196 {
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 23
24 24
25 // Automatically set on build 25 // Automatically set on build
26 -NSString *ASIHTTPRequestVersion = @"v1.6.2-9 2010-05-02"; 26 +NSString *ASIHTTPRequestVersion = @"v1.6.2-10 2010-05-02";
27 27
28 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; 28 NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain";
29 29
@@ -145,6 +145,8 @@ static id <ASICacheDelegate> defaultCache = nil; @@ -145,6 +145,8 @@ static id <ASICacheDelegate> defaultCache = nil;
145 145
146 - (void)updateStatus:(NSTimer*)timer; 146 - (void)updateStatus:(NSTimer*)timer;
147 147
  148 +- (BOOL)useDataFromCache;
  149 +
148 #if TARGET_OS_IPHONE 150 #if TARGET_OS_IPHONE
149 + (void)registerForNetworkReachabilityNotifications; 151 + (void)registerForNetworkReachabilityNotifications;
150 + (void)unsubscribeFromNetworkReachabilityNotifications; 152 + (void)unsubscribeFromNetworkReachabilityNotifications;
@@ -798,15 +800,20 @@ static id <ASICacheDelegate> defaultCache = nil; @@ -798,15 +800,20 @@ static id <ASICacheDelegate> defaultCache = nil;
798 return; 800 return;
799 } 801 }
800 802
801 - // See if we should pull from the cache rather than fetching the data 803 + if ([self downloadCache]) {
802 - if ([self downloadCache] && [self cachePolicy] == ASIOnlyLoadIfNotCachedCachePolicy) { 804 + if ([self cachePolicy] == ASIDefaultCachePolicy) {
803 - if ([[self downloadCache] useDataFromCacheForRequest:self]) { 805 + [self setCachePolicy:[[self downloadCache] defaultCachePolicy]];
804 - [[self cancelledLock] unlock]; 806 + }
805 - return; 807 +
  808 + // See if we should pull from the cache rather than fetching the data
  809 + if ([self cachePolicy] == ASIOnlyLoadIfNotCachedCachePolicy) {
  810 + if ([self useDataFromCache]) {
  811 + [[self cancelledLock] unlock];
  812 + return;
  813 + }
806 } 814 }
807 } 815 }
808 816
809 -  
810 [self requestStarted]; 817 [self requestStarted];
811 818
812 [self setDownloadComplete:NO]; 819 [self setDownloadComplete:NO];
@@ -1573,11 +1580,7 @@ static id <ASICacheDelegate> defaultCache = nil; @@ -1573,11 +1580,7 @@ static id <ASICacheDelegate> defaultCache = nil;
1573 } 1580 }
1574 1581
1575 if ([self downloadCache] && [self cachePolicy] == ASIUseCacheIfLoadFailsCachePolicy) { 1582 if ([self downloadCache] && [self cachePolicy] == ASIUseCacheIfLoadFailsCachePolicy) {
1576 - if ([[self downloadCache] useDataFromCacheForRequest:self]) { 1583 + if ([self useDataFromCache]) {
1577 - [self markAsFinished];  
1578 - if ([self mainRequest]) {  
1579 - [[self mainRequest] markAsFinished];  
1580 - }  
1581 return; 1584 return;
1582 } 1585 }
1583 } 1586 }
@@ -1638,10 +1641,8 @@ static id <ASICacheDelegate> defaultCache = nil; @@ -1638,10 +1641,8 @@ static id <ASICacheDelegate> defaultCache = nil;
1638 CFRelease(headerFields); 1641 CFRelease(headerFields);
1639 1642
1640 if ([self downloadCache] && [self cachePolicy] == ASIReloadIfDifferentCachePolicy) { 1643 if ([self downloadCache] && [self cachePolicy] == ASIReloadIfDifferentCachePolicy) {
1641 - if ([[self downloadCache] useDataFromCacheForRequest:self]) { 1644 + if ([self useDataFromCache]) {
1642 CFRelease(message); 1645 CFRelease(message);
1643 - [self cancelLoad];  
1644 - [self markAsFinished];  
1645 return; 1646 return;
1646 } 1647 }
1647 } 1648 }
@@ -2662,6 +2663,45 @@ static id <ASICacheDelegate> defaultCache = nil; @@ -2662,6 +2663,45 @@ static id <ASICacheDelegate> defaultCache = nil;
2662 CFRunLoopStop(CFRunLoopGetCurrent()); 2663 CFRunLoopStop(CFRunLoopGetCurrent());
2663 } 2664 }
2664 2665
  2666 +- (BOOL)useDataFromCache
  2667 +{
  2668 + NSDictionary *headers = [[self downloadCache] cachedHeadersForRequest:self];
  2669 + if (!headers) {
  2670 + return NO;
  2671 + }
  2672 + NSString *dataPath = [[self downloadCache] pathToCachedResponseDataForRequest:self];
  2673 + if (!dataPath) {
  2674 + return NO;
  2675 + }
  2676 +
  2677 + [self cancelLoad];
  2678 +
  2679 + ASIHTTPRequest *theRequest = self;
  2680 + if ([self mainRequest]) {
  2681 + theRequest = [self mainRequest];
  2682 + }
  2683 + [theRequest setResponseHeaders:headers];
  2684 + if ([theRequest downloadDestinationPath]) {
  2685 + [theRequest setDownloadDestinationPath:dataPath];
  2686 + } else {
  2687 + [theRequest setRawResponseData:[NSMutableData dataWithData:[[self downloadCache] cachedResponseDataForRequest:self]]];
  2688 + }
  2689 + [theRequest setContentLength:[[[self responseHeaders] objectForKey:@"Content-Length"] longLongValue]];
  2690 + [theRequest setTotalBytesRead:[self contentLength]];
  2691 +
  2692 +
  2693 + [theRequest setComplete:YES];
  2694 + [theRequest setDownloadComplete:YES];
  2695 +
  2696 + [theRequest updateProgressIndicators];
  2697 + [theRequest requestFinished];
  2698 + [theRequest markAsFinished];
  2699 + if ([self mainRequest]) {
  2700 + [self markAsFinished];
  2701 + }
  2702 + return YES;
  2703 +}
  2704 +
2665 - (BOOL)retryUsingNewConnection 2705 - (BOOL)retryUsingNewConnection
2666 { 2706 {
2667 if ([self retryCount] == 0) { 2707 if ([self retryCount] == 0) {
@@ -40,6 +40,11 @@ @@ -40,6 +40,11 @@
40 40
41 ASIHTTPRequest *bigFetchRequest; 41 ASIHTTPRequest *bigFetchRequest;
42 IBOutlet NSTextField *postStatus; 42 IBOutlet NSTextField *postStatus;
  43 +
  44 + IBOutlet NSTableView *tableView;
  45 + IBOutlet NSTextField *tableLoadStatus;
  46 + NSMutableArray *rowData;
  47 + ASINetworkQueue *tableQueue;
43 } 48 }
44 49
45 - (IBAction)simpleURLFetch:(id)sender; 50 - (IBAction)simpleURLFetch:(id)sender;
@@ -57,6 +62,10 @@ @@ -57,6 +62,10 @@
57 62
58 - (IBAction)throttleBandwidth:(id)sender; 63 - (IBAction)throttleBandwidth:(id)sender;
59 64
  65 +- (IBAction)reloadTableData:(id)sender;
  66 +- (IBAction)clearCache:(id)sender;
60 67
61 @property (retain, nonatomic) ASIHTTPRequest *bigFetchRequest; 68 @property (retain, nonatomic) ASIHTTPRequest *bigFetchRequest;
  69 +@property (retain, nonatomic) NSMutableArray *rowData;
  70 +@property (retain, nonatomic) ASINetworkQueue *tableQueue;
62 @end 71 @end
@@ -9,6 +9,7 @@ @@ -9,6 +9,7 @@
9 #import "ASIHTTPRequest.h" 9 #import "ASIHTTPRequest.h"
10 #import "ASIFormDataRequest.h" 10 #import "ASIFormDataRequest.h"
11 #import "ASINetworkQueue.h" 11 #import "ASINetworkQueue.h"
  12 +#import "ASIDownloadCache.h"
12 13
13 @interface AppDelegate () 14 @interface AppDelegate ()
14 - (void)updateBandwidthUsageIndicator; 15 - (void)updateBandwidthUsageIndicator;
@@ -25,6 +26,7 @@ @@ -25,6 +26,7 @@
25 26
26 @implementation AppDelegate 27 @implementation AppDelegate
27 28
  29 +
28 - (id)init 30 - (id)init
29 { 31 {
30 [super init]; 32 [super init];
@@ -307,6 +309,82 @@ @@ -307,6 +309,82 @@
307 [postStatus setStringValue:[NSString stringWithFormat:@"Post Failed: %@",[[request error] localizedDescription]]]; 309 [postStatus setStringValue:[NSString stringWithFormat:@"Post Failed: %@",[[request error] localizedDescription]]];
308 } 310 }
309 311
  312 +- (void)tabView:(NSTabView *)tabView didSelectTabViewItem:(NSTabViewItem *)tabViewItem
  313 +{
  314 + if ([[tabViewItem label] isEqualToString:@"Cache"]) {
  315 + [self reloadTableData:nil];
  316 + }
  317 +}
  318 +
  319 +- (IBAction)reloadTableData:(id)sender
  320 +{
  321 + [[self tableQueue] cancelAllOperations];
  322 + [self setTableQueue:[ASINetworkQueue queue]];
  323 + [[ASIDownloadCache sharedCache] setDefaultCachePolicy:ASIOnlyLoadIfNotCachedCachePolicy];
  324 +
  325 + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/table-row-data.xml"]];
  326 + [request setDownloadCache:[ASIDownloadCache sharedCache]];
  327 + [request setDidFinishSelector:@selector(tableViewDataFetchFinished:)];
  328 + [request setDelegate:self];
  329 + [[self tableQueue] addOperation:request];
  330 + [[self tableQueue] go];
  331 +}
  332 +
  333 +- (void)tableViewDataFetchFailed:(ASIHTTPRequest *)request
  334 +{
  335 + if ([[request error] domain] != NetworkRequestErrorDomain || ![[request error] code] == ASIRequestCancelledErrorType) {
  336 + [tableLoadStatus setStringValue:@"Loading data failed"];
  337 + }
  338 +}
  339 +
  340 +- (void)tableViewDataFetchFinished:(ASIHTTPRequest *)request
  341 +{
  342 + [self setRowData:[NSMutableArray array]];
  343 + NSXMLDocument *xml = [[[NSXMLDocument alloc] initWithData:[request responseData] options:NSXMLDocumentValidate error:nil] autorelease];
  344 + for (NSXMLElement *row in [[xml rootElement] elementsForName:@"row"]) {
  345 + NSMutableDictionary *rowInfo = [NSMutableDictionary dictionary];
  346 + NSString *description = [[[row elementsForName:@"description"] objectAtIndex:0] stringValue];
  347 + [rowInfo setValue:description forKey:@"description"];
  348 + NSString *imageURL = [[[row elementsForName:@"image"] objectAtIndex:0] stringValue];
  349 + ASIHTTPRequest *imageRequest = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:imageURL]];
  350 + [imageRequest setDownloadCache:[ASIDownloadCache sharedCache]];
  351 + [imageRequest setDidFinishSelector:@selector(rowImageDownloadFinished:)];
  352 + [imageRequest setDidFailSelector:@selector(tableViewDataFetchFailed:)];
  353 + [imageRequest setDelegate:self];
  354 + [imageRequest setUserInfo:rowInfo];
  355 + [[self tableQueue] addOperation:imageRequest];
  356 + [[self rowData] addObject:rowInfo];
  357 + }
  358 + [tableView reloadData];
  359 +}
  360 +
  361 +- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
  362 +{
  363 + return [[self rowData] count];
  364 +}
  365 +
  366 +- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
  367 +{
  368 + if ([[aTableColumn identifier] isEqualToString:@"image"]) {
  369 + return [[[self rowData] objectAtIndex:rowIndex] objectForKey:@"image"];
  370 + } else {
  371 + return [[[self rowData] objectAtIndex:rowIndex] objectForKey:@"description"];
  372 + }
  373 +}
  374 +
  375 +- (void)rowImageDownloadFinished:(ASIHTTPRequest *)request
  376 +{
  377 + [(NSMutableDictionary *)[request userInfo] setObject:[[[NSImage alloc] initWithData:[request responseData]] autorelease] forKey:@"image"];
  378 + [tableView reloadData]; // Not efficient, but I hate table view programming :)
  379 +}
  380 +
  381 +- (IBAction)clearCache:(id)sender
  382 +{
  383 + [[ASIDownloadCache sharedCache] clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
  384 +}
  385 +
310 386
311 @synthesize bigFetchRequest; 387 @synthesize bigFetchRequest;
  388 +@synthesize rowData;
  389 +@synthesize tableQueue;
312 @end 390 @end
This diff is collapsed. Click to expand it.