2222#import < netinet/in.h>
2323#import < netinet6/in6.h>
2424
25+ int kNeverRevalidate = INT_MAX ;
26+ int kAlwaysRevalidate = 0 ;
27+
28+ int kNeverExpire = INT_MAX ;
29+ int kImmediatelyExpire = 0 ;
30+
2531#define kJSONCacheDirectory @" JSONCache"
2632#define kDefaultExpirationTimeInHours 1 // 1 day
2733#define kDefaultExpirationTimeInHoursWhenOffline 168 // 1 week
2834
35+ #define kIfModifiedSinceHTTPHeaderName @" If-Modified-Since"
36+ #define kETagHTTPHeaderName @" ETag"
37+
38+ #define kIfModifiedSinceHTTPHeaderNameXd @" X-If-Modified-Since"
39+ #define kETagHTTPHeaderNameXd @" X-ETag"
40+
2941static JSONCache* instance = nil ;
3042
3143#pragma mark - cache implementation
@@ -40,6 +52,9 @@ @implementation JSONCache
4052 NSTimeInterval _expirationOnlineTimeInterval;
4153 NSTimeInterval _expirationOfflineTimeInterval;
4254
55+ NSTimeInterval _revalidateFromServerTimeInterval;
56+ NSTimeInterval _revalidateFromServerWithETagTimeInterval;
57+
4358 SCNetworkReachabilityRef reachabilityRef;
4459 BOOL observingConnection;
4560}
@@ -68,6 +83,12 @@ -(id)_initPrivate
6883 self.expirationTimeInHours = kDefaultExpirationTimeInHours ;
6984 self.expirationTimeInHoursWhenOffline = kDefaultExpirationTimeInHoursWhenOffline ;
7085
86+ self.revalidateCacheFromServerAfterTimeInHours = kNeverRevalidate ;
87+ self.revalidateCacheViaETagAfterTimeInHours = kNeverRevalidate ;
88+
89+ self.isUsingXdHTTPHeaderNames = YES ;
90+ _isOnline = YES ;
91+
7192 // setup the directories
7293 NSArray *paths = NSSearchPathForDirectoriesInDomains (NSCachesDirectory , NSUserDomainMask, YES );
7394 cacheDirectory = [[paths objectAtIndex: 0 ] stringByAppendingPathComponent: kJSONCacheDirectory ];
@@ -88,12 +109,13 @@ -(id)_initPrivate
88109
89110 // read the current cache contents
90111 for (NSString * fileName in directoryContents) {
112+ JMLog (@" cache file: %@ " , fileName);
91113 NSString * fullFilePath = [cacheDirectory stringByAppendingPathComponent: fileName];
92114 NSDate * modifiedDate = [[fm attributesOfItemAtPath: fullFilePath error: NULL ] fileModificationDate ];
93115
94116 JSONCacheFile* file = [[JSONCacheFile alloc ] init ];
95117 file.modificationTime = [modifiedDate timeIntervalSinceReferenceDate ];
96- file.name = fileName;
118+ file.fileName = fileName;
97119
98120 [cacheFiles setValue: file forKey: fileName];
99121 }
@@ -105,14 +127,20 @@ -(id)_initPrivate
105127 if (cacheFiles.count >0 && observingConnection==NO ) {
106128 // observe connectivity changes
107129 [self startConnectivityObserver ];
108- JMLog (@" JSONCache.isOnline = %@ " , _isOnline?@" YES" :@" NO" );
130+ JMLog (@" JSONCache.isOnline = %@ " , _isOnline?@" YES" :@" NO" );
109131 }
110132 }
111133
112134 return self;
113135}
114136
115137-(BOOL )addObject : (id )object forMethod : (NSString *)method andParams : (id )params
138+ {
139+ return [self addObject: object forMethod: method andParams: params etag: nil ];
140+ }
141+
142+
143+ -(BOOL )addObject : (id )object forMethod : (NSString *)method andParams : (id )params etag : (NSString *)etag
116144{
117145 // expire objects
118146 [self trimExpiredObjects ];
@@ -127,9 +155,7 @@ -(BOOL)addObject:(id)object forMethod:(NSString*)method andParams:(id)params
127155 BOOL success = [data writeToFile: filePath atomically: YES ];
128156 if (success) {
129157 // save the cache file
130- JSONCacheFile* file = [[JSONCacheFile alloc ] init ];
131- file.name = key;
132- file.modificationTime = [[NSDate date ] timeIntervalSinceReferenceDate ];
158+ JSONCacheFile* file = [[JSONCacheFile alloc ] initWithKey: key andEtag: etag];
133159 [cacheFiles setValue: file forKey: key];
134160 }
135161
@@ -142,7 +168,7 @@ -(BOOL)addObject:(id)object forMethod:(NSString*)method andParams:(id)params
142168 return success;
143169}
144170
145- -(id )objectForMethod : (NSString *)method andParams : (id )params
171+ -(JSONCacheResponse* )objectForMethod : (NSString *)method andParams : (id )params
146172{
147173 NSString * key = [self keyForMethod: method andParams: params];
148174 JSONCacheFile* file = cacheFiles[key];
@@ -157,8 +183,52 @@ -(id)objectForMethod:(NSString*)method andParams:(id)params
157183
158184 NSString * filePath = [cacheDirectory stringByAppendingPathComponent: key];
159185 NSData * data = [NSData dataWithContentsOfFile: filePath];
160- id result = [NSKeyedUnarchiver unarchiveObjectWithData: data];
161- return result;
186+
187+ id object;
188+ @try {
189+ object = [NSKeyedUnarchiver unarchiveObjectWithData: data];
190+ } @catch (NSException * e) {
191+ return nil ;
192+ }
193+
194+ JSONCacheResponse* response = [[JSONCacheResponse alloc ] init ];
195+ response.object = object;
196+ response.isOfflineVersion = !self.isOnline ;
197+
198+ if (self.isOnline && self.revalidateCacheFromServerAfterTimeInHours !=kNeverRevalidate ) {
199+
200+ // check for revalidation rules
201+ NSTimeInterval now = [[NSDate date ] timeIntervalSinceReferenceDate ];
202+ NSTimeInterval expiration = _revalidateFromServerTimeInterval;
203+ if (now > file.modificationTime + expiration) {
204+ response.mustRevalidate = YES ;
205+
206+ NSDateFormatter *dateFormatter = [[NSDateFormatter alloc ] init ];
207+ [dateFormatter setDateFormat: @" yyyy-MM-dd'T'HH:mm:ssZZZZ" ];
208+ NSDate * lastModificationDate = [NSDate dateWithTimeIntervalSinceReferenceDate: file.modificationTime];
209+
210+ [response.revalidateHeaders setValue: [dateFormatter stringFromDate: lastModificationDate]
211+ forKey: self .isUsingXdHTTPHeaderNames? kIfModifiedSinceHTTPHeaderNameXd: kIfModifiedSinceHTTPHeaderName ];
212+ }
213+ }
214+
215+ if (self.isOnline && self.revalidateCacheViaETagAfterTimeInHours !=kNeverRevalidate ) {
216+ // check for the revalidation rules
217+ NSTimeInterval now = [[NSDate date ] timeIntervalSinceReferenceDate ];
218+ NSTimeInterval expiration = now + _revalidateFromServerWithETagTimeInterval;
219+
220+ if (now > file.modificationTime + expiration) {
221+ response.mustRevalidate = YES ;
222+
223+ if (file.etag != nil ) {
224+ [response.revalidateHeaders setValue: file.etag
225+ forKey: self .isUsingXdHTTPHeaderNames? kETagHTTPHeaderNameXd: kETagHTTPHeaderName ];
226+ }
227+ }
228+ }
229+
230+ NSLog (@" CASHED RESULT!!!" );
231+ return response;
162232}
163233
164234-(NSString *)keyForMethod : (NSString *)method andParams : (id )params
@@ -176,14 +246,15 @@ -(void)trimExpiredObjects
176246{
177247 for (NSString * key in [cacheFiles allKeys ]) {
178248 if ([self isObjectExpired: cacheFiles[key]]) {
179- // expired content
249+ // expired content
180250 [self trimObjectForKey: key];
181251 }
182252 }
183253}
184254
185255-(void )trimObjectForKey : (NSString *)key
186256{
257+ NSLog (@" delete file: %@ " , key);
187258 NSString * filePath = [cacheDirectory stringByAppendingPathComponent: key];
188259 [fm removeItemAtPath: filePath error: NULL ];
189260 [cacheFiles removeObjectForKey: key];
@@ -286,22 +357,19 @@ -(BOOL)isObjectExpired:(JSONCacheFile*)object
286357 NSTimeInterval expiration = 0 ;
287358
288359 if (self.isOnline ==NO && self.isOfflineCacheEnabled ==NO ) {
289-
360+ NSLog ( @" expire %@ for Offline:offline disabled " , object. key );
290361 // it's always expired
291362 return YES ;
292363
293364 } else if (self.isOnline ==YES && self.isOfflineCacheEnabled ==NO ) {
294-
295365 // online cache expiration
296366 expiration = _expirationOnlineTimeInterval;
297367
298368 } else if (self.isOnline ==YES && self.isOfflineCacheEnabled ==YES ) {
299-
300369 // offline expiration time
301370 expiration = _expirationOfflineTimeInterval;
302371
303372 } else {
304-
305373 // just return the longer of the two exp. times
306374 expiration = MAX (_expirationOfflineTimeInterval, _expirationOnlineTimeInterval);
307375
@@ -342,12 +410,16 @@ -(void)setExpirationTimeInHoursWhenOffline:(int)expirationTimeInHoursWhenOffline
342410 _expirationOfflineTimeInterval = expirationTimeInHoursWhenOffline * 360 ;
343411}
344412
345- @end
413+ -(void )setRevalidateCacheFromServerAfterTimeInHours : (int )hrs
414+ {
415+ _revalidateCacheFromServerAfterTimeInHours = hrs;
416+ _revalidateFromServerTimeInterval = hrs * 360 ;
417+ }
346418
347- #pragma mark - helper classes
348- @implementation JSONCacheFile
349- -(NSString *)description
419+ -(void )setRevalidateCacheViaETagAfterTimeInHours : (int )hrs
350420{
351- return self.name ;
421+ _revalidateCacheViaETagAfterTimeInHours = hrs;
422+ _revalidateFromServerWithETagTimeInterval = hrs * 360 ;
352423}
353- @end
424+
425+ @end
0 commit comments