Statistics
| Branch: | Tag: | Revision:

root / Classes / ASIDownloadCache.m @ 72744ed1

History | View | Annotate | Download (16.2 kB)

1 700184fb Miltiadis Vasilakis
//
2 700184fb Miltiadis Vasilakis
//  ASIDownloadCache.m
3 700184fb Miltiadis Vasilakis
//  Part of ASIHTTPRequest -> http://allseeing-i.com/ASIHTTPRequest
4 700184fb Miltiadis Vasilakis
//
5 700184fb Miltiadis Vasilakis
//  Created by Ben Copsey on 01/05/2010.
6 700184fb Miltiadis Vasilakis
//  Copyright 2010 All-Seeing Interactive. All rights reserved.
7 700184fb Miltiadis Vasilakis
//
8 700184fb Miltiadis Vasilakis
9 700184fb Miltiadis Vasilakis
#import "ASIDownloadCache.h"
10 700184fb Miltiadis Vasilakis
#import "ASIHTTPRequest.h"
11 700184fb Miltiadis Vasilakis
#import <CommonCrypto/CommonHMAC.h>
12 700184fb Miltiadis Vasilakis
13 700184fb Miltiadis Vasilakis
static ASIDownloadCache *sharedCache = nil;
14 700184fb Miltiadis Vasilakis
15 700184fb Miltiadis Vasilakis
static NSString *sessionCacheFolder = @"SessionStore";
16 700184fb Miltiadis Vasilakis
static NSString *permanentCacheFolder = @"PermanentStore";
17 700184fb Miltiadis Vasilakis
18 700184fb Miltiadis Vasilakis
@interface ASIDownloadCache ()
19 700184fb Miltiadis Vasilakis
+ (NSString *)keyForURL:(NSURL *)url;
20 700184fb Miltiadis Vasilakis
@end
21 700184fb Miltiadis Vasilakis
22 700184fb Miltiadis Vasilakis
@implementation ASIDownloadCache
23 700184fb Miltiadis Vasilakis
24 700184fb Miltiadis Vasilakis
- (id)init
25 700184fb Miltiadis Vasilakis
{
26 700184fb Miltiadis Vasilakis
	self = [super init];
27 700184fb Miltiadis Vasilakis
	[self setShouldRespectCacheControlHeaders:YES];
28 700184fb Miltiadis Vasilakis
	[self setDefaultCachePolicy:ASIUseDefaultCachePolicy];
29 700184fb Miltiadis Vasilakis
	[self setAccessLock:[[[NSRecursiveLock alloc] init] autorelease]];
30 700184fb Miltiadis Vasilakis
	return self;
31 700184fb Miltiadis Vasilakis
}
32 700184fb Miltiadis Vasilakis
33 700184fb Miltiadis Vasilakis
+ (id)sharedCache
34 700184fb Miltiadis Vasilakis
{
35 700184fb Miltiadis Vasilakis
	if (!sharedCache) {
36 700184fb Miltiadis Vasilakis
		sharedCache = [[self alloc] init];
37 700184fb Miltiadis Vasilakis
		[sharedCache setStoragePath:[[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:@"ASIHTTPRequestCache"]];
38 700184fb Miltiadis Vasilakis
39 700184fb Miltiadis Vasilakis
	}
40 700184fb Miltiadis Vasilakis
	return sharedCache;
41 700184fb Miltiadis Vasilakis
}
42 700184fb Miltiadis Vasilakis
43 700184fb Miltiadis Vasilakis
- (void)dealloc
44 700184fb Miltiadis Vasilakis
{
45 700184fb Miltiadis Vasilakis
	[storagePath release];
46 700184fb Miltiadis Vasilakis
	[accessLock release];
47 700184fb Miltiadis Vasilakis
	[super dealloc];
48 700184fb Miltiadis Vasilakis
}
49 700184fb Miltiadis Vasilakis
50 700184fb Miltiadis Vasilakis
- (NSString *)storagePath
51 700184fb Miltiadis Vasilakis
{
52 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
53 700184fb Miltiadis Vasilakis
	NSString *p = [[storagePath retain] autorelease];
54 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
55 700184fb Miltiadis Vasilakis
	return p;
56 700184fb Miltiadis Vasilakis
}
57 700184fb Miltiadis Vasilakis
58 700184fb Miltiadis Vasilakis
59 700184fb Miltiadis Vasilakis
- (void)setStoragePath:(NSString *)path
60 700184fb Miltiadis Vasilakis
{
61 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
62 700184fb Miltiadis Vasilakis
	[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
63 700184fb Miltiadis Vasilakis
	[storagePath release];
64 700184fb Miltiadis Vasilakis
	storagePath = [path retain];
65 700184fb Miltiadis Vasilakis
	BOOL isDirectory = NO;
66 700184fb Miltiadis Vasilakis
	NSArray *directories = [NSArray arrayWithObjects:path,[path stringByAppendingPathComponent:sessionCacheFolder],[path stringByAppendingPathComponent:permanentCacheFolder],nil];
67 700184fb Miltiadis Vasilakis
	for (NSString *directory in directories) {
68 700184fb Miltiadis Vasilakis
		BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:directory isDirectory:&isDirectory];
69 700184fb Miltiadis Vasilakis
		if (exists && !isDirectory) {
70 700184fb Miltiadis Vasilakis
			[[self accessLock] unlock];
71 700184fb Miltiadis Vasilakis
			[NSException raise:@"FileExistsAtCachePath" format:@"Cannot create a directory for the cache at '%@', because a file already exists",directory];
72 700184fb Miltiadis Vasilakis
		} else if (!exists) {
73 700184fb Miltiadis Vasilakis
			[[NSFileManager defaultManager] createDirectoryAtPath:directory withIntermediateDirectories:NO attributes:nil error:nil];
74 700184fb Miltiadis Vasilakis
			if (![[NSFileManager defaultManager] fileExistsAtPath:directory]) {
75 700184fb Miltiadis Vasilakis
				[[self accessLock] unlock];
76 700184fb Miltiadis Vasilakis
				[NSException raise:@"FailedToCreateCacheDirectory" format:@"Failed to create a directory for the cache at '%@'",directory];
77 700184fb Miltiadis Vasilakis
			}
78 700184fb Miltiadis Vasilakis
		}
79 700184fb Miltiadis Vasilakis
	}
80 700184fb Miltiadis Vasilakis
	[self clearCachedResponsesForStoragePolicy:ASICacheForSessionDurationCacheStoragePolicy];
81 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
82 700184fb Miltiadis Vasilakis
}
83 700184fb Miltiadis Vasilakis
84 700184fb Miltiadis Vasilakis
- (void)storeResponseForRequest:(ASIHTTPRequest *)request maxAge:(NSTimeInterval)maxAge
85 700184fb Miltiadis Vasilakis
{
86 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
87 700184fb Miltiadis Vasilakis
	
88 700184fb Miltiadis Vasilakis
	if ([request error] || ![request responseHeaders] || ([request responseStatusCode] != 200) || ([request cachePolicy] & ASIDoNotWriteToCacheCachePolicy)) {
89 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
90 700184fb Miltiadis Vasilakis
		return;
91 700184fb Miltiadis Vasilakis
	}
92 700184fb Miltiadis Vasilakis
	
93 700184fb Miltiadis Vasilakis
	if ([self shouldRespectCacheControlHeaders] && ![[self class] serverAllowsResponseCachingForRequest:request]) {
94 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
95 700184fb Miltiadis Vasilakis
		return;
96 700184fb Miltiadis Vasilakis
	}
97 700184fb Miltiadis Vasilakis
98 700184fb Miltiadis Vasilakis
	NSString *headerPath = [self pathToStoreCachedResponseHeadersForRequest:request];
99 700184fb Miltiadis Vasilakis
	NSString *dataPath = [self pathToStoreCachedResponseDataForRequest:request];
100 700184fb Miltiadis Vasilakis
	
101 700184fb Miltiadis Vasilakis
	NSMutableDictionary *responseHeaders = [NSMutableDictionary dictionaryWithDictionary:[request responseHeaders]];
102 700184fb Miltiadis Vasilakis
	if ([request isResponseCompressed]) {
103 700184fb Miltiadis Vasilakis
		[responseHeaders removeObjectForKey:@"Content-Encoding"];
104 700184fb Miltiadis Vasilakis
	}
105 700184fb Miltiadis Vasilakis
	if (maxAge != 0) {
106 700184fb Miltiadis Vasilakis
		[responseHeaders removeObjectForKey:@"Expires"];
107 700184fb Miltiadis Vasilakis
		[responseHeaders setObject:[NSString stringWithFormat:@"max-age=%i",(int)maxAge] forKey:@"Cache-Control"];
108 700184fb Miltiadis Vasilakis
	}
109 700184fb Miltiadis Vasilakis
	// We use this special key to help expire the request when we get a max-age header
110 700184fb Miltiadis Vasilakis
	[responseHeaders setObject:[[[self class] rfc1123DateFormatter] stringFromDate:[NSDate date]] forKey:@"X-ASIHTTPRequest-Fetch-date"];
111 700184fb Miltiadis Vasilakis
	[responseHeaders writeToFile:headerPath atomically:NO];
112 700184fb Miltiadis Vasilakis
	
113 700184fb Miltiadis Vasilakis
	if ([request responseData]) {
114 700184fb Miltiadis Vasilakis
		[[request responseData] writeToFile:dataPath atomically:NO];
115 700184fb Miltiadis Vasilakis
	} else if ([request downloadDestinationPath] && ![[request downloadDestinationPath] isEqualToString:dataPath]) {
116 700184fb Miltiadis Vasilakis
		NSError *error = nil;
117 700184fb Miltiadis Vasilakis
		[[NSFileManager defaultManager] copyItemAtPath:[request downloadDestinationPath] toPath:dataPath error:&error];
118 700184fb Miltiadis Vasilakis
	}
119 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
120 700184fb Miltiadis Vasilakis
}
121 700184fb Miltiadis Vasilakis
122 700184fb Miltiadis Vasilakis
- (NSDictionary *)cachedResponseHeadersForURL:(NSURL *)url
123 700184fb Miltiadis Vasilakis
{
124 700184fb Miltiadis Vasilakis
	NSString *path = [self pathToCachedResponseHeadersForURL:url];
125 700184fb Miltiadis Vasilakis
	if (path) {
126 700184fb Miltiadis Vasilakis
		return [NSDictionary dictionaryWithContentsOfFile:path];
127 700184fb Miltiadis Vasilakis
	}
128 700184fb Miltiadis Vasilakis
	return nil;
129 700184fb Miltiadis Vasilakis
}
130 700184fb Miltiadis Vasilakis
131 700184fb Miltiadis Vasilakis
- (NSData *)cachedResponseDataForURL:(NSURL *)url
132 700184fb Miltiadis Vasilakis
{
133 700184fb Miltiadis Vasilakis
	NSString *path = [self pathToCachedResponseDataForURL:url];
134 700184fb Miltiadis Vasilakis
	if (path) {
135 700184fb Miltiadis Vasilakis
		return [NSData dataWithContentsOfFile:path];
136 700184fb Miltiadis Vasilakis
	}
137 700184fb Miltiadis Vasilakis
	return nil;
138 700184fb Miltiadis Vasilakis
}
139 700184fb Miltiadis Vasilakis
140 700184fb Miltiadis Vasilakis
- (NSString *)pathToCachedResponseDataForURL:(NSURL *)url
141 700184fb Miltiadis Vasilakis
{
142 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
143 700184fb Miltiadis Vasilakis
	if (![self storagePath]) {
144 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
145 700184fb Miltiadis Vasilakis
		return nil;
146 700184fb Miltiadis Vasilakis
	}
147 700184fb Miltiadis Vasilakis
	// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view 
148 700184fb Miltiadis Vasilakis
	NSString *extension = [[url path] pathExtension];
149 700184fb Miltiadis Vasilakis
	if (![extension length]) {
150 700184fb Miltiadis Vasilakis
		extension = @"html";
151 700184fb Miltiadis Vasilakis
	}
152 700184fb Miltiadis Vasilakis
153 700184fb Miltiadis Vasilakis
	// Look in the session store
154 700184fb Miltiadis Vasilakis
	NSString *path = [[self storagePath] stringByAppendingPathComponent:sessionCacheFolder];
155 700184fb Miltiadis Vasilakis
	NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]];
156 700184fb Miltiadis Vasilakis
	if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
157 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
158 700184fb Miltiadis Vasilakis
		return dataPath;
159 700184fb Miltiadis Vasilakis
	}
160 700184fb Miltiadis Vasilakis
	// Look in the permanent store
161 700184fb Miltiadis Vasilakis
	path = [[self storagePath] stringByAppendingPathComponent:permanentCacheFolder];
162 700184fb Miltiadis Vasilakis
	dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:extension]];
163 700184fb Miltiadis Vasilakis
	if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
164 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
165 700184fb Miltiadis Vasilakis
		return dataPath;
166 700184fb Miltiadis Vasilakis
	}
167 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
168 700184fb Miltiadis Vasilakis
	return nil;
169 700184fb Miltiadis Vasilakis
}
170 700184fb Miltiadis Vasilakis
171 700184fb Miltiadis Vasilakis
- (NSString *)pathToCachedResponseHeadersForURL:(NSURL *)url
172 700184fb Miltiadis Vasilakis
{
173 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
174 700184fb Miltiadis Vasilakis
	if (![self storagePath]) {
175 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
176 700184fb Miltiadis Vasilakis
		return nil;
177 700184fb Miltiadis Vasilakis
	}
178 700184fb Miltiadis Vasilakis
	// Look in the session store
179 700184fb Miltiadis Vasilakis
	NSString *path = [[self storagePath] stringByAppendingPathComponent:sessionCacheFolder];
180 700184fb Miltiadis Vasilakis
	NSString *dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]];
181 700184fb Miltiadis Vasilakis
	if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
182 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
183 700184fb Miltiadis Vasilakis
		return dataPath;
184 700184fb Miltiadis Vasilakis
	}
185 700184fb Miltiadis Vasilakis
	// Look in the permanent store
186 700184fb Miltiadis Vasilakis
	path = [[self storagePath] stringByAppendingPathComponent:permanentCacheFolder];
187 700184fb Miltiadis Vasilakis
	dataPath = [path stringByAppendingPathComponent:[[[self class] keyForURL:url] stringByAppendingPathExtension:@"cachedheaders"]];
188 700184fb Miltiadis Vasilakis
	if ([[NSFileManager defaultManager] fileExistsAtPath:dataPath]) {
189 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
190 700184fb Miltiadis Vasilakis
		return dataPath;
191 700184fb Miltiadis Vasilakis
	}
192 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
193 700184fb Miltiadis Vasilakis
	return nil;
194 700184fb Miltiadis Vasilakis
}
195 700184fb Miltiadis Vasilakis
196 700184fb Miltiadis Vasilakis
- (NSString *)pathToStoreCachedResponseDataForRequest:(ASIHTTPRequest *)request
197 700184fb Miltiadis Vasilakis
{
198 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
199 700184fb Miltiadis Vasilakis
	if (![self storagePath]) {
200 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
201 700184fb Miltiadis Vasilakis
		return nil;
202 700184fb Miltiadis Vasilakis
	}
203 700184fb Miltiadis Vasilakis
204 700184fb Miltiadis Vasilakis
	NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
205 700184fb Miltiadis Vasilakis
206 700184fb Miltiadis Vasilakis
	// Grab the file extension, if there is one. We do this so we can save the cached response with the same file extension - this is important if you want to display locally cached data in a web view 
207 700184fb Miltiadis Vasilakis
	NSString *extension = [[[request url] path] pathExtension];
208 700184fb Miltiadis Vasilakis
	if (![extension length]) {
209 700184fb Miltiadis Vasilakis
		extension = @"html";
210 700184fb Miltiadis Vasilakis
	}
211 700184fb Miltiadis Vasilakis
	path =  [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:extension]];
212 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
213 700184fb Miltiadis Vasilakis
	return path;
214 700184fb Miltiadis Vasilakis
}
215 700184fb Miltiadis Vasilakis
216 700184fb Miltiadis Vasilakis
- (NSString *)pathToStoreCachedResponseHeadersForRequest:(ASIHTTPRequest *)request
217 700184fb Miltiadis Vasilakis
{
218 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
219 700184fb Miltiadis Vasilakis
	if (![self storagePath]) {
220 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
221 700184fb Miltiadis Vasilakis
		return nil;
222 700184fb Miltiadis Vasilakis
	}
223 700184fb Miltiadis Vasilakis
	NSString *path = [[self storagePath] stringByAppendingPathComponent:([request cacheStoragePolicy] == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
224 700184fb Miltiadis Vasilakis
	path =  [path stringByAppendingPathComponent:[[[self class] keyForURL:[request url]] stringByAppendingPathExtension:@"cachedheaders"]];
225 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
226 700184fb Miltiadis Vasilakis
	return path;
227 700184fb Miltiadis Vasilakis
}
228 700184fb Miltiadis Vasilakis
229 700184fb Miltiadis Vasilakis
230 700184fb Miltiadis Vasilakis
- (void)removeCachedDataForRequest:(ASIHTTPRequest *)request
231 700184fb Miltiadis Vasilakis
{
232 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
233 700184fb Miltiadis Vasilakis
	if (![self storagePath]) {
234 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
235 700184fb Miltiadis Vasilakis
		return;
236 700184fb Miltiadis Vasilakis
	}
237 700184fb Miltiadis Vasilakis
238 700184fb Miltiadis Vasilakis
	NSString *cachedHeadersPath = [self pathToCachedResponseHeadersForURL:[request url]];
239 700184fb Miltiadis Vasilakis
	if (!cachedHeadersPath) {
240 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
241 700184fb Miltiadis Vasilakis
		return;
242 700184fb Miltiadis Vasilakis
	}
243 700184fb Miltiadis Vasilakis
	NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
244 700184fb Miltiadis Vasilakis
	if (!dataPath) {
245 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
246 700184fb Miltiadis Vasilakis
		return;
247 700184fb Miltiadis Vasilakis
	}
248 700184fb Miltiadis Vasilakis
	[[NSFileManager defaultManager] removeItemAtPath:cachedHeadersPath error:NULL];
249 700184fb Miltiadis Vasilakis
	[[NSFileManager defaultManager] removeItemAtPath:dataPath error:NULL];
250 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
251 700184fb Miltiadis Vasilakis
}
252 700184fb Miltiadis Vasilakis
253 700184fb Miltiadis Vasilakis
- (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request
254 700184fb Miltiadis Vasilakis
{
255 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
256 700184fb Miltiadis Vasilakis
	if (![self storagePath]) {
257 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
258 700184fb Miltiadis Vasilakis
		return NO;
259 700184fb Miltiadis Vasilakis
	}
260 700184fb Miltiadis Vasilakis
	NSDictionary *cachedHeaders = [self cachedResponseHeadersForURL:[request url]];
261 700184fb Miltiadis Vasilakis
	if (!cachedHeaders) {
262 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
263 700184fb Miltiadis Vasilakis
		return NO;
264 700184fb Miltiadis Vasilakis
	}
265 700184fb Miltiadis Vasilakis
	NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
266 700184fb Miltiadis Vasilakis
	if (!dataPath) {
267 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
268 700184fb Miltiadis Vasilakis
		return NO;
269 700184fb Miltiadis Vasilakis
	}
270 700184fb Miltiadis Vasilakis
271 700184fb Miltiadis Vasilakis
	// If we already have response headers for this request, check to see if the new content is different
272 700184fb Miltiadis Vasilakis
	if ([request responseHeaders]) {
273 700184fb Miltiadis Vasilakis
274 700184fb Miltiadis Vasilakis
		// New content is not different
275 700184fb Miltiadis Vasilakis
		if ([request responseStatusCode] == 304) {
276 700184fb Miltiadis Vasilakis
			[[self accessLock] unlock];
277 700184fb Miltiadis Vasilakis
			return YES;
278 700184fb Miltiadis Vasilakis
		}
279 700184fb Miltiadis Vasilakis
280 700184fb Miltiadis Vasilakis
		// If the Etag or Last-Modified date are different from the one we have, we'll have to fetch this resource again
281 700184fb Miltiadis Vasilakis
		NSArray *headersToCompare = [NSArray arrayWithObjects:@"Etag",@"Last-Modified",nil];
282 700184fb Miltiadis Vasilakis
		for (NSString *header in headersToCompare) {
283 700184fb Miltiadis Vasilakis
			if (![[[request responseHeaders] objectForKey:header] isEqualToString:[cachedHeaders objectForKey:header]]) {
284 700184fb Miltiadis Vasilakis
				[[self accessLock] unlock];
285 700184fb Miltiadis Vasilakis
				return NO;
286 700184fb Miltiadis Vasilakis
			}
287 700184fb Miltiadis Vasilakis
		}
288 700184fb Miltiadis Vasilakis
	}
289 700184fb Miltiadis Vasilakis
290 700184fb Miltiadis Vasilakis
	if ([self shouldRespectCacheControlHeaders]) {
291 700184fb Miltiadis Vasilakis
292 700184fb Miltiadis Vasilakis
		// Look for an Expires header to see if the content is out of date
293 700184fb Miltiadis Vasilakis
		NSString *expires = [cachedHeaders objectForKey:@"Expires"];
294 700184fb Miltiadis Vasilakis
		if (expires) {
295 700184fb Miltiadis Vasilakis
			if ([[ASIHTTPRequest dateFromRFC1123String:expires] timeIntervalSinceNow] >= 0) {
296 700184fb Miltiadis Vasilakis
				[[self accessLock] unlock];
297 700184fb Miltiadis Vasilakis
				return YES;
298 700184fb Miltiadis Vasilakis
			}
299 700184fb Miltiadis Vasilakis
		}
300 700184fb Miltiadis Vasilakis
		// Look for a max-age header
301 700184fb Miltiadis Vasilakis
		NSString *cacheControl = [[cachedHeaders objectForKey:@"Cache-Control"] lowercaseString];
302 700184fb Miltiadis Vasilakis
		if (cacheControl) {
303 700184fb Miltiadis Vasilakis
			NSScanner *scanner = [NSScanner scannerWithString:cacheControl];
304 700184fb Miltiadis Vasilakis
			if ([scanner scanString:@"max-age" intoString:NULL]) {
305 700184fb Miltiadis Vasilakis
				[scanner scanString:@"=" intoString:NULL];
306 700184fb Miltiadis Vasilakis
				NSTimeInterval maxAge = 0;
307 700184fb Miltiadis Vasilakis
				[scanner scanDouble:&maxAge];
308 700184fb Miltiadis Vasilakis
				NSDate *fetchDate = [ASIHTTPRequest dateFromRFC1123String:[cachedHeaders objectForKey:@"X-ASIHTTPRequest-Fetch-date"]];
309 700184fb Miltiadis Vasilakis
310 700184fb Miltiadis Vasilakis
				NSDate *expiryDate = [[[NSDate alloc] initWithTimeInterval:maxAge sinceDate:fetchDate] autorelease];
311 700184fb Miltiadis Vasilakis
312 700184fb Miltiadis Vasilakis
				if ([expiryDate timeIntervalSinceNow] >= 0) {
313 700184fb Miltiadis Vasilakis
					[[self accessLock] unlock];
314 700184fb Miltiadis Vasilakis
					return YES;
315 700184fb Miltiadis Vasilakis
				}
316 700184fb Miltiadis Vasilakis
			}
317 700184fb Miltiadis Vasilakis
		}
318 700184fb Miltiadis Vasilakis
		
319 700184fb Miltiadis Vasilakis
		// No explicit expiration time sent by the server
320 700184fb Miltiadis Vasilakis
		return NO;
321 700184fb Miltiadis Vasilakis
	}
322 700184fb Miltiadis Vasilakis
	
323 700184fb Miltiadis Vasilakis
324 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
325 700184fb Miltiadis Vasilakis
	return YES;
326 700184fb Miltiadis Vasilakis
}
327 700184fb Miltiadis Vasilakis
328 700184fb Miltiadis Vasilakis
- (ASICachePolicy)defaultCachePolicy
329 700184fb Miltiadis Vasilakis
{
330 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
331 700184fb Miltiadis Vasilakis
	ASICachePolicy cp = defaultCachePolicy;
332 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
333 700184fb Miltiadis Vasilakis
	return cp;
334 700184fb Miltiadis Vasilakis
}
335 700184fb Miltiadis Vasilakis
336 700184fb Miltiadis Vasilakis
337 700184fb Miltiadis Vasilakis
- (void)setDefaultCachePolicy:(ASICachePolicy)cachePolicy
338 700184fb Miltiadis Vasilakis
{
339 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
340 700184fb Miltiadis Vasilakis
	if (!cachePolicy) {
341 700184fb Miltiadis Vasilakis
		defaultCachePolicy = ASIAskServerIfModifiedWhenStaleCachePolicy;
342 700184fb Miltiadis Vasilakis
	}  else {
343 700184fb Miltiadis Vasilakis
		defaultCachePolicy = cachePolicy;	
344 700184fb Miltiadis Vasilakis
	}
345 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
346 700184fb Miltiadis Vasilakis
}
347 700184fb Miltiadis Vasilakis
348 700184fb Miltiadis Vasilakis
- (void)clearCachedResponsesForStoragePolicy:(ASICacheStoragePolicy)storagePolicy
349 700184fb Miltiadis Vasilakis
{
350 700184fb Miltiadis Vasilakis
	[[self accessLock] lock];
351 700184fb Miltiadis Vasilakis
	if (![self storagePath]) {
352 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
353 700184fb Miltiadis Vasilakis
		return;
354 700184fb Miltiadis Vasilakis
	}
355 700184fb Miltiadis Vasilakis
	NSString *path = [[self storagePath] stringByAppendingPathComponent:(storagePolicy == ASICacheForSessionDurationCacheStoragePolicy ? sessionCacheFolder : permanentCacheFolder)];
356 700184fb Miltiadis Vasilakis
357 700184fb Miltiadis Vasilakis
	BOOL isDirectory = NO;
358 700184fb Miltiadis Vasilakis
	BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory];
359 7db1712d Miltiadis Vasilakis
	if ((exists && !isDirectory) || !exists) {
360 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
361 700184fb Miltiadis Vasilakis
		return;
362 700184fb Miltiadis Vasilakis
	}
363 700184fb Miltiadis Vasilakis
	NSError *error = nil;
364 700184fb Miltiadis Vasilakis
	NSArray *cacheFiles = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:&error];
365 700184fb Miltiadis Vasilakis
	if (error) {
366 700184fb Miltiadis Vasilakis
		[[self accessLock] unlock];
367 700184fb Miltiadis Vasilakis
		[NSException raise:@"FailedToTraverseCacheDirectory" format:@"Listing cache directory failed at path '%@'",path];	
368 700184fb Miltiadis Vasilakis
	}
369 700184fb Miltiadis Vasilakis
	for (NSString *file in cacheFiles) {
370 700184fb Miltiadis Vasilakis
		NSString *extension = [file pathExtension];
371 700184fb Miltiadis Vasilakis
		if ([extension isEqualToString:@"cacheddata"] || [extension isEqualToString:@"cachedheaders"]) {
372 700184fb Miltiadis Vasilakis
			[[NSFileManager defaultManager] removeItemAtPath:[path stringByAppendingPathComponent:file] error:&error];
373 700184fb Miltiadis Vasilakis
			if (error) {
374 700184fb Miltiadis Vasilakis
				[[self accessLock] unlock];
375 700184fb Miltiadis Vasilakis
				[NSException raise:@"FailedToRemoveCacheFile" format:@"Failed to remove cached data at path '%@'",path];	
376 700184fb Miltiadis Vasilakis
			}
377 700184fb Miltiadis Vasilakis
		}
378 700184fb Miltiadis Vasilakis
	}
379 700184fb Miltiadis Vasilakis
	[[self accessLock] unlock];
380 700184fb Miltiadis Vasilakis
}
381 700184fb Miltiadis Vasilakis
382 700184fb Miltiadis Vasilakis
+ (BOOL)serverAllowsResponseCachingForRequest:(ASIHTTPRequest *)request
383 700184fb Miltiadis Vasilakis
{
384 700184fb Miltiadis Vasilakis
	NSString *cacheControl = [[[request responseHeaders] objectForKey:@"Cache-Control"] lowercaseString];
385 700184fb Miltiadis Vasilakis
	if (cacheControl) {
386 700184fb Miltiadis Vasilakis
		if ([cacheControl isEqualToString:@"no-cache"] || [cacheControl isEqualToString:@"no-store"]) {
387 700184fb Miltiadis Vasilakis
			return NO;
388 700184fb Miltiadis Vasilakis
		}
389 700184fb Miltiadis Vasilakis
	}
390 700184fb Miltiadis Vasilakis
	NSString *pragma = [[[request responseHeaders] objectForKey:@"Pragma"] lowercaseString];
391 700184fb Miltiadis Vasilakis
	if (pragma) {
392 700184fb Miltiadis Vasilakis
		if ([pragma isEqualToString:@"no-cache"]) {
393 700184fb Miltiadis Vasilakis
			return NO;
394 700184fb Miltiadis Vasilakis
		}
395 700184fb Miltiadis Vasilakis
	}
396 700184fb Miltiadis Vasilakis
	return YES;
397 700184fb Miltiadis Vasilakis
}
398 700184fb Miltiadis Vasilakis
399 700184fb Miltiadis Vasilakis
// Borrowed from: http://stackoverflow.com/questions/652300/using-md5-hash-on-a-string-in-cocoa
400 700184fb Miltiadis Vasilakis
+ (NSString *)keyForURL:(NSURL *)url
401 700184fb Miltiadis Vasilakis
{
402 700184fb Miltiadis Vasilakis
	NSString *urlString = [url absoluteString];
403 700184fb Miltiadis Vasilakis
	// Strip trailing slashes so http://allseeing-i.com/ASIHTTPRequest/ is cached the same as http://allseeing-i.com/ASIHTTPRequest
404 700184fb Miltiadis Vasilakis
	if ([[urlString substringFromIndex:[urlString length]-1] isEqualToString:@"/"]) {
405 700184fb Miltiadis Vasilakis
		urlString = [urlString substringToIndex:[urlString length]-1];
406 700184fb Miltiadis Vasilakis
	}
407 700184fb Miltiadis Vasilakis
	const char *cStr = [urlString UTF8String];
408 700184fb Miltiadis Vasilakis
	unsigned char result[16];
409 700184fb Miltiadis Vasilakis
	CC_MD5(cStr, (CC_LONG)strlen(cStr), result);
410 700184fb Miltiadis Vasilakis
	return [NSString stringWithFormat:@"%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X",result[0], result[1], result[2], result[3], result[4], result[5], result[6], result[7],result[8], result[9], result[10], result[11],result[12], result[13], result[14], result[15]]; 	
411 700184fb Miltiadis Vasilakis
}
412 700184fb Miltiadis Vasilakis
413 700184fb Miltiadis Vasilakis
+ (NSDateFormatter *)rfc1123DateFormatter
414 700184fb Miltiadis Vasilakis
{
415 700184fb Miltiadis Vasilakis
	NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
416 700184fb Miltiadis Vasilakis
	NSDateFormatter *dateFormatter = [threadDict objectForKey:@"ASIDownloadCacheDateFormatter"];
417 700184fb Miltiadis Vasilakis
	if (dateFormatter == nil) {
418 700184fb Miltiadis Vasilakis
		dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
419 700184fb Miltiadis Vasilakis
		[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"] autorelease]];
420 700184fb Miltiadis Vasilakis
		[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];
421 700184fb Miltiadis Vasilakis
		[dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss 'GMT'"];
422 700184fb Miltiadis Vasilakis
		[threadDict setObject:dateFormatter forKey:@"ASIDownloadCacheDateFormatter"];
423 700184fb Miltiadis Vasilakis
	}
424 700184fb Miltiadis Vasilakis
	return dateFormatter;
425 700184fb Miltiadis Vasilakis
}
426 700184fb Miltiadis Vasilakis
427 700184fb Miltiadis Vasilakis
428 700184fb Miltiadis Vasilakis
- (BOOL)canUseCachedDataForRequest:(ASIHTTPRequest *)request
429 700184fb Miltiadis Vasilakis
{
430 700184fb Miltiadis Vasilakis
	// Ensure the request is allowed to read from the cache
431 700184fb Miltiadis Vasilakis
	if ([request cachePolicy] & ASIDoNotReadFromCacheCachePolicy) {
432 700184fb Miltiadis Vasilakis
		return NO;
433 700184fb Miltiadis Vasilakis
434 700184fb Miltiadis Vasilakis
	// If we don't want to load the request whatever happens, always pretend we have cached data even if we don't
435 700184fb Miltiadis Vasilakis
	} else if ([request cachePolicy] & ASIDontLoadCachePolicy) {
436 700184fb Miltiadis Vasilakis
		return YES;
437 700184fb Miltiadis Vasilakis
	}
438 700184fb Miltiadis Vasilakis
439 700184fb Miltiadis Vasilakis
	NSDictionary *headers = [self cachedResponseHeadersForURL:[request url]];
440 700184fb Miltiadis Vasilakis
	if (!headers) {
441 700184fb Miltiadis Vasilakis
		return NO;
442 700184fb Miltiadis Vasilakis
	}
443 700184fb Miltiadis Vasilakis
	NSString *dataPath = [self pathToCachedResponseDataForURL:[request url]];
444 700184fb Miltiadis Vasilakis
	if (!dataPath) {
445 700184fb Miltiadis Vasilakis
		return NO;
446 700184fb Miltiadis Vasilakis
	}
447 700184fb Miltiadis Vasilakis
448 700184fb Miltiadis Vasilakis
	// If we get here, we have cached data
449 700184fb Miltiadis Vasilakis
450 700184fb Miltiadis Vasilakis
	// If we have cached data, we can use it
451 700184fb Miltiadis Vasilakis
	if ([request cachePolicy] & ASIOnlyLoadIfNotCachedCachePolicy) {
452 700184fb Miltiadis Vasilakis
		return YES;
453 700184fb Miltiadis Vasilakis
454 700184fb Miltiadis Vasilakis
	// If we have cached data that is current, we can use it
455 700184fb Miltiadis Vasilakis
	} else if ([request cachePolicy] & ASIAskServerIfModifiedWhenStaleCachePolicy) {
456 700184fb Miltiadis Vasilakis
		if ([self isCachedDataCurrentForRequest:request]) {
457 700184fb Miltiadis Vasilakis
			return YES;
458 700184fb Miltiadis Vasilakis
		}
459 700184fb Miltiadis Vasilakis
460 700184fb Miltiadis Vasilakis
	// If we've got headers from a conditional GET and the cached data is still current, we can use it
461 700184fb Miltiadis Vasilakis
	} else if ([request cachePolicy] & ASIAskServerIfModifiedCachePolicy) {
462 700184fb Miltiadis Vasilakis
		if (![request responseHeaders]) {
463 700184fb Miltiadis Vasilakis
			return NO;
464 700184fb Miltiadis Vasilakis
		} else if ([self isCachedDataCurrentForRequest:request]) {
465 700184fb Miltiadis Vasilakis
			return YES;
466 700184fb Miltiadis Vasilakis
		}
467 700184fb Miltiadis Vasilakis
	}
468 700184fb Miltiadis Vasilakis
	return NO;
469 700184fb Miltiadis Vasilakis
}
470 700184fb Miltiadis Vasilakis
471 700184fb Miltiadis Vasilakis
@synthesize storagePath;
472 700184fb Miltiadis Vasilakis
@synthesize defaultCachePolicy;
473 700184fb Miltiadis Vasilakis
@synthesize accessLock;
474 700184fb Miltiadis Vasilakis
@synthesize shouldRespectCacheControlHeaders;
475 700184fb Miltiadis Vasilakis
@end