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 |