diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a67353a --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# xcode noise +*.mode1v3 +*.pbxuser +*.perspective +*.perspectivev3 +*.pyc +*~.nib/ +build/* + +# Textmate - if you build your xcode projects with it +*.tm_build_errors + +# osx noise +.DS_Store +profile diff --git a/SDURLCache.h b/SDURLCache.h new file mode 100644 index 0000000..0b66e13 --- /dev/null +++ b/SDURLCache.h @@ -0,0 +1,29 @@ +// +// SDURLCache.h +// SDURLCache +// +// Created by Olivier Poitrey on 15/03/10. +// Copyright 2010 Dailymotion. All rights reserved. +// + +#import + +@interface SDURLCache : NSURLCache +{ + @private + NSString *diskCachePath; + NSMutableDictionary *diskCacheInfo; + NSUInteger diskCacheUsage; + NSTimeInterval minCacheInterval; + NSOperationQueue *cacheInQueue; +} + +/* + * Defines the minimum number of seconds between now and the expiration time of a cacheable response + * in order for the response to be cached on disk. This prevent from spending time and storage capacity + * for an entry which will certainly expire before behing read back from disk cache (memory cache is + * best suited for short term cache). The default value is set to 5 minutes (300 seconds). + */ +@property (nonatomic, assign) NSTimeInterval minCacheInterval; + +@end diff --git a/SDURLCache.m b/SDURLCache.m new file mode 100644 index 0000000..808972c --- /dev/null +++ b/SDURLCache.m @@ -0,0 +1,363 @@ +// +// SDURLCache.m +// SDURLCache +// +// Created by Olivier Poitrey on 15/03/10. +// Copyright 2010 Dailymotion. All rights reserved. +// + +#import "SDURLCache.h" +#import +#import + +static NSTimeInterval const kSDURLCacheInfoDefaultMinCacheInterval = 5 * 60; // 5 minute +static NSString *const kSDURLCacheInfoFileName = @"cacheInfo.plist"; +static NSString *const kSDURLCacheInfoDiskUsageKey = @"diskUsage"; +static NSString *const kSDURLCacheInfoExpiresKey = @"expires"; +static NSString *const kSDURLCacheInfoAccessesKey = @"accesses"; +static NSString *const kSDURLCacheInfoSizesKey = @"sizes"; + +@interface SDURLCache () +@property (nonatomic, retain) NSString *diskCachePath; +@property (nonatomic, retain) NSDictionary *diskCacheInfo; +@property (nonatomic, retain) NSOperationQueue *cacheInQueue; +@end + +@implementation SDURLCache + +@synthesize diskCachePath, diskCacheInfo, minCacheInterval, cacheInQueue; + +#pragma mark SDURLCache (private) + +- (NSString *)cacheKeyForURL:(NSURL *)url +{ + const char *str = [url.absoluteString UTF8String]; + unsigned char r[CC_MD5_DIGEST_LENGTH]; + CC_MD5(str, strlen(str), r); + return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", + r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]]; +} + +/* + * This method tries to determine the expiration date based on a response headers dictionary. + */ ++ (NSDate *)expirationDateFromHeaders:(NSDictionary *)headers +{ + // Check Pragma: no-cache + NSString *pragma = [headers objectForKey:@"Pragma"]; + if (pragma && [pragma isEqualToString:@"no-cache"]) + { + // Uncacheable response + return nil; + } + + // Look at info from the Cache-Control: max-age=n header + NSString *cacheControl = [headers objectForKey:@"Cache-Control"]; + if (cacheControl) + { + NSRange foundRange = [cacheControl rangeOfString:@"no-cache"]; + if (foundRange.length > 0) + { + // Can't be cached + return nil; + } + + NSInteger maxAge; + foundRange = [cacheControl rangeOfString:@"max-age="]; + if (foundRange.length > 0) + { + NSScanner *cacheControlScanner = [NSScanner scannerWithString:cacheControl]; + [cacheControlScanner setScanLocation:foundRange.location + foundRange.length]; + if ([cacheControlScanner scanInteger:&maxAge]) + { + if (maxAge > 0) + { + return [NSDate dateWithTimeIntervalSinceNow:maxAge]; + } + else + { + return nil; + } + + } + } + } + + // If not Cache-Control found, look at the Expires header + NSString *expires = [headers objectForKey:@"Expires"]; + if (expires) + { + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"EEE, MMM d, yyyy, h:mm a"]; + NSDate *expirationDate = [dateFormatter dateFromString:expires]; + [dateFormatter release]; + if ([expirationDate timeIntervalSinceNow] < 0) + { + return nil; + } + else + { + return expirationDate; + } + } + + return nil; +} + +- (void)saveCacheInfo +{ + [diskCacheInfo writeToFile:[diskCachePath stringByAppendingFormat:kSDURLCacheInfoFileName] atomically:YES]; +} + +- (void)removeCachedResponseForCachedKeys:(NSArray *)cacheKeys +{ + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + + NSEnumerator *enumerator = [cacheKeys objectEnumerator]; + NSString *cacheKey; + + NSMutableDictionary *expirations = [diskCacheInfo objectForKey:kSDURLCacheInfoExpiresKey]; + NSMutableDictionary *accesses = [diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey]; + NSMutableDictionary *sizes = [diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey]; + + while (cacheKey = [enumerator nextObject]) + { + NSMutableDictionary *cacheInfo = [diskCacheInfo objectForKey:cacheKey]; + + if (cacheInfo) + { + NSNumber *cacheItemSize = [(NSMutableDictionary *)[diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey] objectForKey:cacheKey]; + [expirations removeObjectForKey:cacheKey]; + [accesses removeObjectForKey:cacheKey]; + [sizes removeObjectForKey:cacheKey]; + [[NSFileManager defaultManager] removeItemAtPath:[diskCachePath stringByAppendingPathComponent:cacheKey] error:NULL]; + diskCacheUsage -= [cacheItemSize unsignedIntegerValue]; + [diskCacheInfo setObject:[NSNumber numberWithUnsignedInteger:diskCacheUsage] forKey:kSDURLCacheInfoDiskUsageKey]; + } + } + + [pool drain]; +} + +- (void)balanceDiskUsage +{ + // Clean all expired keys + NSDictionary *expirations = [diskCacheInfo objectForKey:kSDURLCacheInfoExpiresKey]; + NSMutableArray *keysToRemove = [NSMutableArray array]; + + NSArray *sortedKeys = [expirations keysSortedByValueUsingSelector:@selector(compare:)]; + NSEnumerator *enumerator = [sortedKeys objectEnumerator]; + NSString *cacheKey; + while ((cacheKey = [enumerator nextObject]) && [(NSDate *)[expirations objectForKey:cacheKey] timeIntervalSinceNow] < 0) + { + [keysToRemove addObject:cacheKey]; + } + + if ([keysToRemove count] > 0) + { + [self removeCachedResponseForCachedKeys:keysToRemove]; + + if (diskCacheUsage < self.diskCapacity) + { + [self saveCacheInfo]; + return; + } + } + else if(diskCacheUsage < self.diskCapacity) + { + return; + } + + // Clean least recently used keys until disk usage outreach capacity + NSDictionary *sizes = [diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey]; + keysToRemove = [NSMutableArray array]; + + NSInteger capacityToSave = diskCacheUsage - self.diskCapacity; + sortedKeys = [(NSDictionary *)[diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey] keysSortedByValueUsingSelector:@selector(compare:)]; + enumerator = [sortedKeys objectEnumerator]; + while (capacityToSave > 0 && (cacheKey = [enumerator nextObject])) + { + [keysToRemove addObject:cacheKey]; + capacityToSave -= [(NSNumber *)[sizes objectForKey:cacheKey] unsignedIntegerValue]; + } + + [self removeCachedResponseForCachedKeys:keysToRemove]; + [self saveCacheInfo]; +} + + +- (void)storeToDisk:(NSDictionary *)context +{ + NSURLRequest *request = [context objectForKey:@"request"]; + NSCachedURLResponse *cachedResponse = [context objectForKey:@"cachedResponse"]; + NSDate *expirationDate = [context objectForKey:@"expirationDate"]; + + NSString *cacheKey = [self cacheKeyForURL:request.URL]; + NSString *cacheFilePath = [diskCachePath stringByAppendingPathComponent:cacheKey]; + + // Archive the cached response on disk + if (![NSKeyedArchiver archiveRootObject:cachedResponse toFile:cacheFilePath]) + { + // Caching failed for some reason + return; + } + + // Update disk usage info + NSNumber *cacheItemSize = [[[NSFileManager defaultManager] fileAttributesAtPath:cacheFilePath traverseLink:NO] objectForKey:NSFileSize]; + diskCacheUsage += [cacheItemSize unsignedIntegerValue]; + [diskCacheInfo setObject:[NSNumber numberWithUnsignedInteger:diskCacheUsage] forKey:kSDURLCacheInfoDiskUsageKey]; + + + // Update cache info for the stored item + [(NSMutableDictionary *)[diskCacheInfo objectForKey:kSDURLCacheInfoExpiresKey] setObject:expirationDate forKey:cacheKey]; + [(NSMutableDictionary *)[diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey] setObject:[NSDate date] forKey:cacheKey]; + [(NSMutableDictionary *)[diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey] setObject:cacheItemSize forKey:cacheKey]; + + if (diskCacheUsage > self.diskCapacity) + { + [self balanceDiskUsage]; + } + else + { + [self saveCacheInfo]; + } +} + +#pragma mark SDURLCache (notification handlers) + +- (void)applicationWillTerminate +{ +} + +#pragma mark NSURLCache + +- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path +{ + if ((self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path])) + { + self.minCacheInterval = kSDURLCacheInfoDefaultMinCacheInterval; + self.diskCachePath = path; + + if (![[NSFileManager defaultManager] fileExistsAtPath:diskCachePath]) + { + [[NSFileManager defaultManager] createDirectoryAtPath:diskCachePath attributes:nil]; + } + + self.diskCacheInfo = [NSMutableDictionary dictionaryWithContentsOfFile:[diskCachePath stringByAppendingFormat:kSDURLCacheInfoFileName]]; + if (!self.diskCacheInfo) + { + self.diskCacheInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys: + [NSNumber numberWithUnsignedInt:0], kSDURLCacheInfoDiskUsageKey, + [NSMutableDictionary dictionary], kSDURLCacheInfoExpiresKey, + [NSMutableDictionary dictionary], kSDURLCacheInfoAccessesKey, + [NSMutableDictionary dictionary], kSDURLCacheInfoSizesKey, + nil]; + } + + // Init the operation queue + self.cacheInQueue = [[[NSOperationQueue alloc] init] autorelease]; + cacheInQueue.maxConcurrentOperationCount = 1; // used to streamline operations in a separate thread + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationWillTerminate) + name:UIApplicationWillTerminateNotification + object:nil]; + } + + return self; +} + +- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request +{ + [super storeCachedResponse:cachedResponse forRequest:request]; + + if (cachedResponse.storagePolicy == NSURLCacheStorageAllowed + && [cachedResponse.response isKindOfClass:[NSHTTPURLResponse self]] + && cachedResponse.data.length < self.diskCapacity) + { + NSDate *expirationDate = [SDURLCache expirationDateFromHeaders:[(NSHTTPURLResponse *)cachedResponse.response allHeaderFields]]; + if (!expirationDate || [expirationDate timeIntervalSinceNow] - minCacheInterval <= 0) + { + // This response is not cacheable, headers said + return; + } + + [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self + selector:@selector(storeToDisk:) + object:[NSDictionary dictionaryWithObjectsAndKeys: + cachedResponse, @"cachedResponse", + request, @"request", + expirationDate, @"expirationDate", + nil]] autorelease]]; + } +} + +- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request +{ + NSString *cacheKey = [self cacheKeyForURL:request.URL]; + NSDate *expirationDate = [(NSDictionary *)[diskCacheInfo objectForKey:kSDURLCacheInfoExpiresKey] objectForKey:cacheKey]; + + if (expirationDate && [expirationDate timeIntervalSinceNow] < 0) + { + [self removeCachedResponseForRequest:request]; + return [super cachedResponseForRequest:request]; + } + + NSCachedURLResponse *memoryResponse = [super cachedResponseForRequest:request]; + if (memoryResponse) + { + return memoryResponse; + } + + NSCachedURLResponse *cachedResponse = [NSKeyedUnarchiver unarchiveObjectWithFile:[diskCachePath stringByAppendingPathComponent:cacheKey]]; + if (cachedResponse) + { + [(NSMutableDictionary *)[diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey] setObject:[NSDate date] forKey:cacheKey]; + return cachedResponse; + } + + return nil; +} + +- (void)setDiskCapacity:(NSUInteger)diskCapacity +{ + [super setDiskCapacity:diskCapacity]; + + if (diskCacheUsage > diskCapacity) + { + [cacheInQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self + selector:@selector(balanceDiskUsage) + object:nil] autorelease]]; + } +} + +- (NSUInteger)currentDiskUsage +{ + return diskCacheUsage; +} + +- (void)removeCachedResponseForRequest:(NSURLRequest *)request +{ + [super removeCachedResponseForRequest:request]; + [self removeCachedResponseForCachedKeys:[NSArray arrayWithObject:[self cacheKeyForURL:request.URL]]]; + [self saveCacheInfo]; +} + +- (void)removeAllCachedResponses +{ + [super removeAllCachedResponses]; +} + +#pragma mark NSObject + +- (void)dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [diskCachePath release]; + [diskCacheInfo release]; + [cacheInQueue release]; + [super dealloc]; +} + + +@end diff --git a/SDURLCache.xcodeproj/project.pbxproj b/SDURLCache.xcodeproj/project.pbxproj new file mode 100644 index 0000000..5f58aa8 --- /dev/null +++ b/SDURLCache.xcodeproj/project.pbxproj @@ -0,0 +1,391 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 45; + objects = { + +/* Begin PBXBuildFile section */ + 53F557F4114EA63600A3DA4B /* SDURLCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53F557F2114EA63600A3DA4B /* SDURLCache.h */; }; + 53F557F5114EA63600A3DA4B /* SDURLCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 53F557F3114EA63600A3DA4B /* SDURLCache.m */; }; + 53F55801114EB2E100A3DA4B /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53F55800114EB2E100A3DA4B /* UIKit.framework */; }; + 53F5592C114F1ED800A3DA4B /* SDURLCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 53F5592B114F1ED800A3DA4B /* SDURLCacheTests.m */; }; + 53F559B7114F2AA600A3DA4B /* libSDURLCache.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AAC07E0554694100DB518D /* libSDURLCache.a */; }; + AA747D9F0F9514B9006C5449 /* SDURLCache_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* SDURLCache_Prefix.pch */; }; + AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AACBBE490F95108600F1A2B1 /* Foundation.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 53F559C2114F2ABF00A3DA4B /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D2AAC07D0554694100DB518D /* SDURLCache */; + remoteInfo = SDURLCache; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 53F557F2114EA63600A3DA4B /* SDURLCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDURLCache.h; sourceTree = ""; }; + 53F557F3114EA63600A3DA4B /* SDURLCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDURLCache.m; sourceTree = ""; }; + 53F55800114EB2E100A3DA4B /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + 53F5591C114F1D5E00A3DA4B /* SDURLCacheTestBundle.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SDURLCacheTestBundle.octest; sourceTree = BUILT_PRODUCTS_DIR; }; + 53F5592A114F1ED800A3DA4B /* SDURLCacheTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDURLCacheTests.h; sourceTree = ""; }; + 53F5592B114F1ED800A3DA4B /* SDURLCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDURLCacheTests.m; sourceTree = ""; }; + AA747D9E0F9514B9006C5449 /* SDURLCache_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDURLCache_Prefix.pch; sourceTree = SOURCE_ROOT; }; + AACBBE490F95108600F1A2B1 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + D2AAC07E0554694100DB518D /* libSDURLCache.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSDURLCache.a; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 53F55919114F1D5E00A3DA4B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 53F559B7114F2AA600A3DA4B /* libSDURLCache.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07C0554694100DB518D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + AACBBE4A0F95108600F1A2B1 /* Foundation.framework in Frameworks */, + 53F55801114EB2E100A3DA4B /* UIKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 034768DFFF38A50411DB9C8B /* Products */ = { + isa = PBXGroup; + children = ( + D2AAC07E0554694100DB518D /* libSDURLCache.a */, + 53F5591C114F1D5E00A3DA4B /* SDURLCacheTestBundle.octest */, + ); + name = Products; + sourceTree = ""; + }; + 0867D691FE84028FC02AAC07 /* SDURLCache */ = { + isa = PBXGroup; + children = ( + 08FB77AEFE84172EC02AAC07 /* Classes */, + 32C88DFF0371C24200C91783 /* Other Sources */, + 53F55929114F1EA300A3DA4B /* Tests */, + 0867D69AFE84028FC02AAC07 /* Frameworks */, + 034768DFFF38A50411DB9C8B /* Products */, + ); + name = SDURLCache; + sourceTree = ""; + }; + 0867D69AFE84028FC02AAC07 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 53F55800114EB2E100A3DA4B /* UIKit.framework */, + AACBBE490F95108600F1A2B1 /* Foundation.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 08FB77AEFE84172EC02AAC07 /* Classes */ = { + isa = PBXGroup; + children = ( + 53F557F2114EA63600A3DA4B /* SDURLCache.h */, + 53F557F3114EA63600A3DA4B /* SDURLCache.m */, + ); + name = Classes; + sourceTree = ""; + }; + 32C88DFF0371C24200C91783 /* Other Sources */ = { + isa = PBXGroup; + children = ( + AA747D9E0F9514B9006C5449 /* SDURLCache_Prefix.pch */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 53F55929114F1EA300A3DA4B /* Tests */ = { + isa = PBXGroup; + children = ( + 53F5592A114F1ED800A3DA4B /* SDURLCacheTests.h */, + 53F5592B114F1ED800A3DA4B /* SDURLCacheTests.m */, + ); + name = Tests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D2AAC07A0554694100DB518D /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + AA747D9F0F9514B9006C5449 /* SDURLCache_Prefix.pch in Headers */, + 53F557F4114EA63600A3DA4B /* SDURLCache.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 53F5591B114F1D5E00A3DA4B /* SDURLCacheTestBundle */ = { + isa = PBXNativeTarget; + buildConfigurationList = 53F55920114F1D5F00A3DA4B /* Build configuration list for PBXNativeTarget "SDURLCacheTestBundle" */; + buildPhases = ( + 53F55917114F1D5E00A3DA4B /* Resources */, + 53F55918114F1D5E00A3DA4B /* Sources */, + 53F55919114F1D5E00A3DA4B /* Frameworks */, + 53F5591A114F1D5E00A3DA4B /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 53F559C3114F2ABF00A3DA4B /* PBXTargetDependency */, + ); + name = SDURLCacheTestBundle; + productName = SDURLCacheTestBundle; + productReference = 53F5591C114F1D5E00A3DA4B /* SDURLCacheTestBundle.octest */; + productType = "com.apple.product-type.bundle"; + }; + D2AAC07D0554694100DB518D /* SDURLCache */ = { + isa = PBXNativeTarget; + buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "SDURLCache" */; + buildPhases = ( + D2AAC07A0554694100DB518D /* Headers */, + D2AAC07B0554694100DB518D /* Sources */, + D2AAC07C0554694100DB518D /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = SDURLCache; + productName = SDURLCache; + productReference = D2AAC07E0554694100DB518D /* libSDURLCache.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 0867D690FE84028FC02AAC07 /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "SDURLCache" */; + compatibilityVersion = "Xcode 3.1"; + hasScannedForEncodings = 1; + mainGroup = 0867D691FE84028FC02AAC07 /* SDURLCache */; + productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D2AAC07D0554694100DB518D /* SDURLCache */, + 53F5591B114F1D5E00A3DA4B /* SDURLCacheTestBundle */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 53F55917114F1D5E00A3DA4B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 53F5591A114F1D5E00A3DA4B /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 53F55918114F1D5E00A3DA4B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 53F5592C114F1ED800A3DA4B /* SDURLCacheTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D2AAC07B0554694100DB518D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 53F557F5114EA63600A3DA4B /* SDURLCache.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 53F559C3114F2ABF00A3DA4B /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D2AAC07D0554694100DB518D /* SDURLCache */; + targetProxy = 53F559C2114F2ABF00A3DA4B /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 1DEB921F08733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + COPY_PHASE_STRIP = NO; + DSTROOT = /tmp/SDURLCache.dst; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_MODEL_TUNING = G5; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SDURLCache_Prefix.pch; + INSTALL_PATH = /usr/local/lib; + PRODUCT_NAME = SDURLCache; + }; + name = Debug; + }; + 1DEB922008733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + DSTROOT = /tmp/SDURLCache.dst; + GCC_MODEL_TUNING = G5; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = SDURLCache_Prefix.pch; + INSTALL_PATH = /usr/local/lib; + PRODUCT_NAME = SDURLCache; + }; + name = Release; + }; + 1DEB922308733DC00010E9CD /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_LDFLAGS = "-ObjC"; + PREBINDING = NO; + SDKROOT = iphoneos3.2; + }; + name = Debug; + }; + 1DEB922408733DC00010E9CD /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = "$(ARCHS_STANDARD_32_BIT)"; + GCC_C_LANGUAGE_STANDARD = c99; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + OTHER_LDFLAGS = "-ObjC"; + PREBINDING = NO; + SDKROOT = iphoneos3.2; + }; + name = Release; + }; + 53F5591E114F1D5F00A3DA4B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + SenTestingKit, + "-framework", + UIKit, + ); + PREBINDING = NO; + PRODUCT_NAME = SDURLCacheTestBundle; + WRAPPER_EXTENSION = octest; + }; + name = Debug; + }; + 53F5591F114F1D5F00A3DA4B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; + COPY_PHASE_STRIP = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + FRAMEWORK_SEARCH_PATHS = ( + "\"$(SDKROOT)/Developer/Library/Frameworks\"", + "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", + ); + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_ENABLE_OBJC_EXCEPTIONS = YES; + INFOPLIST_FILE = "SDURLCacheTestBundle-Info.plist"; + OTHER_LDFLAGS = ( + "-framework", + Foundation, + "-framework", + SenTestingKit, + "-framework", + UIKit, + ); + PREBINDING = NO; + PRODUCT_NAME = SDURLCacheTestBundle; + WRAPPER_EXTENSION = octest; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "SDURLCache" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB921F08733DC00010E9CD /* Debug */, + 1DEB922008733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "SDURLCache" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 1DEB922308733DC00010E9CD /* Debug */, + 1DEB922408733DC00010E9CD /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 53F55920114F1D5F00A3DA4B /* Build configuration list for PBXNativeTarget "SDURLCacheTestBundle" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 53F5591E114F1D5F00A3DA4B /* Debug */, + 53F5591F114F1D5F00A3DA4B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 0867D690FE84028FC02AAC07 /* Project object */; +} diff --git a/SDURLCacheTests.h b/SDURLCacheTests.h new file mode 100644 index 0000000..dfad113 --- /dev/null +++ b/SDURLCacheTests.h @@ -0,0 +1,27 @@ +// +// SDURLCacheTests.h +// SDURLCache +// +// Created by Olivier Poitrey on 16/03/10. +// Copyright 2010 Dailymotion. All rights reserved. +// +// See Also: http://developer.apple.com/iphone/library/documentation/Xcode/Conceptual/iphone_development/135-Unit_Testing_Applications/unit_testing_applications.html + +// Application unit tests contain unit test code that must be injected into an application to run correctly. +// Define USE_APPLICATION_UNIT_TEST to 0 if the unit test code is designed to be linked into an independent test executable. + +#define USE_APPLICATION_UNIT_TEST 1 + +#import +#import +//#import "application_headers" as required + + +@interface SDURLCacheTests : SenTestCase +{ + +} + +- (void)testExpirationDateFromHeader; + +@end diff --git a/SDURLCacheTests.m b/SDURLCacheTests.m new file mode 100644 index 0000000..48dfc8b --- /dev/null +++ b/SDURLCacheTests.m @@ -0,0 +1,67 @@ +// +// SDURLCacheTests.m +// SDURLCache +// +// Created by Olivier Poitrey on 16/03/10. +// Copyright 2010 Dailymotion. All rights reserved. +// + +#import "SDURLCacheTests.h" +#import "SDURLCache.h" + +@interface SDURLCache () ++ (NSDate *)expirationDateFromHeaders:(NSDictionary *)headers; +@end + +@implementation SDURLCacheTests + +- (void)testExpirationDateFromHeader +{ + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"EEE, MMM d, yyyy, h:mm a"]; + NSString *pastDate = [dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:-1000]]; + NSString *futureDate = [dateFormatter stringFromDate:[NSDate dateWithTimeIntervalSinceNow:1000]]; + + NSDate *expDate; + + // Pragma: no-cache + expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"no-cache", @"Pragma", futureDate, @"Expires", nil]]; + + STAssertNil(expDate, @"Pragma no-cache"); + + // Expires in the past + expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:pastDate, @"Expires", nil]]; + + STAssertNil(expDate, @"Expires in the past"); + + // Cache-Control: no-cache with Expires in the future + expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"no-cache", @"Cache-Control", futureDate, @"Expires", nil]]; + STAssertNil(expDate, @"Cache-Control no-cache with Expires in the future"); + + // Cache-Control with future date + expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=1000", @"Cache-Control", nil]]; + STAssertNotNil(expDate, @"Cache-Control with future date"); + STAssertTrue([expDate timeIntervalSinceNow] > 0, @"Cache-Control with future date"); + + // Cache-Control with max-age=0 and Expires future date + expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=0", @"Cache-Control", + futureDate, @"Expires", nil]]; + STAssertNil(expDate, @"Cache-Control with max-age=0 and Expires future date"); + + // Cache-Control with future date and Expires past date + expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=1000", @"Cache-Control", pastDate, @"Expires", nil]]; + STAssertNotNil(expDate, @"Cache-Control with future date and Expires past date"); + STAssertTrue([expDate timeIntervalSinceNow] > 0, @"Cache-Control with future date and Expires past date"); +} + +- (void)testCaching +{ + // TODO +} + +- (void)testCacheCapacity +{ + // TODO +} + +@end diff --git a/SDURLCache_Prefix.pch b/SDURLCache_Prefix.pch new file mode 100644 index 0000000..bfb7394 --- /dev/null +++ b/SDURLCache_Prefix.pch @@ -0,0 +1,7 @@ +// +// Prefix header for all source files of the 'CocoaTouchStaticLibrary' target in the 'CocoaTouchStaticLibrary' project. +// + +#ifdef __OBJC__ + #import +#endif