add .gitignore
This commit is contained in:
19
msext/Class/webCache/NSString+Sha1.h
Executable file
19
msext/Class/webCache/NSString+Sha1.h
Executable file
@@ -0,0 +1,19 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
|
||||
/**
|
||||
* This extension contains several a helper
|
||||
* for creating a sha1 hash from instances of NSString
|
||||
*/
|
||||
@interface NSString (Sha1)
|
||||
|
||||
/**
|
||||
* Creates a SHA1 (hash) representation of NSString.
|
||||
*
|
||||
* @return NSString
|
||||
*/
|
||||
- (NSString *)sha1;
|
||||
|
||||
|
||||
@end
|
||||
24
msext/Class/webCache/NSString+Sha1.m
Executable file
24
msext/Class/webCache/NSString+Sha1.m
Executable file
@@ -0,0 +1,24 @@
|
||||
|
||||
#import "NSString+Sha1.h"
|
||||
|
||||
@implementation NSString (Sha1)
|
||||
|
||||
- (NSString *)sha1
|
||||
{
|
||||
// see http://www.makebetterthings.com/iphone/how-to-get-md5-and-sha1-in-objective-c-ios-sdk/
|
||||
NSData *data = [self dataUsingEncoding:NSUTF8StringEncoding];
|
||||
uint8_t digest[CC_SHA1_DIGEST_LENGTH];
|
||||
|
||||
CC_SHA1(data.bytes, (CC_LONG)data.length, digest);
|
||||
|
||||
NSMutableString *output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
||||
[output appendFormat:@"%02x", digest[i]];
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
60
msext/Class/webCache/README.md
Executable file
60
msext/Class/webCache/README.md
Executable file
@@ -0,0 +1,60 @@
|
||||
# BACKGROUND
|
||||
|
||||
RNCachingURLProtocol is a simple shim for the HTTP protocol (that’s not
|
||||
nearly as scary as it sounds). Anytime a URL is downloaded, the response is
|
||||
cached to disk. Anytime a URL is requested, if we’re online then things
|
||||
proceed normally. If we’re offline, then we retrieve the cached version.
|
||||
|
||||
The point of RNCachingURLProtocol is mostly to demonstrate how this is done.
|
||||
The current implementation is extremely simple. In particular, it doesn’t
|
||||
worry about cleaning up the cache. The assumption is that you’re caching just
|
||||
a few simple things, like your “Latest News” page (which was the problem I
|
||||
was solving). It caches all HTTP traffic, so without some modifications, it’s
|
||||
not appropriate for an app that has a lot of HTTP connections (see
|
||||
MKNetworkKit for that). But if you need to cache some URLs and not others,
|
||||
that is easy to implement.
|
||||
|
||||
You should also look at [AFCache](https://github.com/artifacts/AFCache) for a
|
||||
more powerful caching engine that is currently integrating the ideas of
|
||||
RNCachingURLProtocol.
|
||||
|
||||
# USAGE
|
||||
|
||||
1. To build, you will need the Reachability code from Apple (included). That requires that you link with
|
||||
`SystemConfiguration.framework`.
|
||||
|
||||
2. At some point early in the program (usually `application:didFinishLaunchingWithOptions:`),
|
||||
call the following:
|
||||
|
||||
`[NSURLProtocol registerClass:[RNCachingURLProtocol class]];`
|
||||
|
||||
3. There is no step 3.
|
||||
|
||||
For more details see
|
||||
[Drop-in offline caching for UIWebView (and NSURLProtocol)](http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588).
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
See the CachedWebView project for example usage.
|
||||
|
||||
# LICENSE
|
||||
|
||||
This code is licensed under the MIT License:
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
71
msext/Class/webCache/RNCachingURLProtocol.h
Executable file
71
msext/Class/webCache/RNCachingURLProtocol.h
Executable file
@@ -0,0 +1,71 @@
|
||||
//
|
||||
// RNCachingURLProtocol.h
|
||||
//
|
||||
// Created by Robert Napier on 1/10/12.
|
||||
// Copyright (c) 2012 Rob Napier. All rights reserved.
|
||||
//
|
||||
// This code is licensed under the MIT License:
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
// RNCachingURLProtocol is a simple shim for the HTTP protocol (that’s not
|
||||
// nearly as scary as it sounds). Anytime a URL is download, the response is
|
||||
// cached to disk. Anytime a URL is requested, if we’re online then things
|
||||
// proceed normally. If we’re offline, then we retrieve the cached version.
|
||||
//
|
||||
// The point of RNCachingURLProtocol is mostly to demonstrate how this is done.
|
||||
// The current implementation is extremely simple. In particular, it doesn’t
|
||||
// worry about cleaning up the cache. The assumption is that you’re caching just
|
||||
// a few simple things, like your “Latest News” page (which was the problem I
|
||||
// was solving). It caches all HTTP traffic, so without some modifications, it’s
|
||||
// not appropriate for an app that has a lot of HTTP connections (see
|
||||
// MKNetworkKit for that). But if you need to cache some URLs and not others,
|
||||
// that is easy to implement.
|
||||
//
|
||||
// You should also look at [AFCache](https://github.com/artifacts/AFCache) for a
|
||||
// more powerful caching engine that is currently integrating the ideas of
|
||||
// RNCachingURLProtocol.
|
||||
//
|
||||
// A quick rundown of how to use it:
|
||||
//
|
||||
// 1. To build, you will need the Reachability code from Apple (included). That requires that you link with
|
||||
// `SystemConfiguration.framework`.
|
||||
//
|
||||
// 2. At some point early in the program (application:didFinishLaunchingWithOptions:),
|
||||
// call the following:
|
||||
//
|
||||
// `[NSURLProtocol registerClass:[RNCachingURLProtocol class]];`
|
||||
//
|
||||
// 3. There is no step 3.
|
||||
//
|
||||
// For more details see
|
||||
// [Drop-in offline caching for UIWebView (and NSURLProtocol)](http://robnapier.net/blog/offline-uiwebview-nsurlprotocol-588).
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface RNCachingURLProtocol : NSURLProtocol
|
||||
|
||||
+ (NSSet *)supportedSchemes;
|
||||
+ (void)setSupportedSchemes:(NSSet *)supportedSchemes;
|
||||
|
||||
- (NSString *)cachePathForRequest:(NSURLRequest *)aRequest;
|
||||
- (BOOL) useCache;
|
||||
|
||||
@end
|
||||
296
msext/Class/webCache/RNCachingURLProtocol.m
Executable file
296
msext/Class/webCache/RNCachingURLProtocol.m
Executable file
@@ -0,0 +1,296 @@
|
||||
//
|
||||
// RNCachingURLProtocol.m
|
||||
//
|
||||
// Created by Robert Napier on 1/10/12.
|
||||
// Copyright (c) 2012 Rob Napier.
|
||||
//
|
||||
// This code is licensed under the MIT License:
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a
|
||||
// copy of this software and associated documentation files (the "Software"),
|
||||
// to deal in the Software without restriction, including without limitation
|
||||
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
// and/or sell copies of the Software, and to permit persons to whom the
|
||||
// Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
//
|
||||
|
||||
#import "RNCachingURLProtocol.h"
|
||||
#import "Reachability.h"
|
||||
#import "NSString+Sha1.h"
|
||||
|
||||
#define WORKAROUND_MUTABLE_COPY_LEAK 1
|
||||
|
||||
#if WORKAROUND_MUTABLE_COPY_LEAK
|
||||
// required to workaround http://openradar.appspot.com/11596316
|
||||
@interface NSURLRequest(MutableCopyWorkaround)
|
||||
|
||||
- (id) mutableCopyWorkaround;
|
||||
|
||||
@end
|
||||
#endif
|
||||
|
||||
@interface RNCachedData : NSObject <NSCoding>
|
||||
@property (nonatomic, readwrite, strong) NSData *data;
|
||||
@property (nonatomic, readwrite, strong) NSURLResponse *response;
|
||||
@property (nonatomic, readwrite, strong) NSURLRequest *redirectRequest;
|
||||
@end
|
||||
|
||||
static NSString *RNCachingURLHeader = @"X-RNCache";
|
||||
|
||||
@interface RNCachingURLProtocol () // <NSURLConnectionDelegate, NSURLConnectionDataDelegate> iOS5-only
|
||||
@property (nonatomic, readwrite, strong) NSURLConnection *connection;
|
||||
@property (nonatomic, readwrite, strong) NSMutableData *data;
|
||||
@property (nonatomic, readwrite, strong) NSURLResponse *response;
|
||||
- (void)appendData:(NSData *)newData;
|
||||
@end
|
||||
|
||||
static NSObject *RNCachingSupportedSchemesMonitor;
|
||||
static NSSet *RNCachingSupportedSchemes;
|
||||
|
||||
@implementation RNCachingURLProtocol
|
||||
@synthesize connection = connection_;
|
||||
@synthesize data = data_;
|
||||
@synthesize response = response_;
|
||||
|
||||
+ (void)initialize
|
||||
{
|
||||
if (self == [RNCachingURLProtocol class])
|
||||
{
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
RNCachingSupportedSchemesMonitor = [NSObject new];
|
||||
});
|
||||
|
||||
[self setSupportedSchemes:[NSSet setWithObject:@"http"]];
|
||||
}
|
||||
}
|
||||
|
||||
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
|
||||
{
|
||||
// only handle http requests we haven't marked with our header.
|
||||
if ([[self supportedSchemes] containsObject:[[request URL] scheme]] &&
|
||||
([request valueForHTTPHeaderField:RNCachingURLHeader] == nil))
|
||||
{
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
|
||||
{
|
||||
return request;
|
||||
}
|
||||
|
||||
- (NSString *)cachePathForRequest:(NSURLRequest *)aRequest
|
||||
{
|
||||
// This stores in the Caches directory, which can be deleted when space is low, but we only use it for offline access
|
||||
NSString *cachesPath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
|
||||
NSString *fileName = [[[aRequest URL] absoluteString] sha1];
|
||||
|
||||
return [cachesPath stringByAppendingPathComponent:fileName];
|
||||
}
|
||||
|
||||
- (void)startLoading
|
||||
{
|
||||
if (![self useCache]) {
|
||||
NSMutableURLRequest *connectionRequest =
|
||||
#if WORKAROUND_MUTABLE_COPY_LEAK
|
||||
[[self request] mutableCopyWorkaround];
|
||||
#else
|
||||
[[self request] mutableCopy];
|
||||
#endif
|
||||
// we need to mark this request with our header so we know not to handle it in +[NSURLProtocol canInitWithRequest:].
|
||||
[connectionRequest setValue:@"" forHTTPHeaderField:RNCachingURLHeader];
|
||||
NSURLConnection *connection = [NSURLConnection connectionWithRequest:connectionRequest
|
||||
delegate:self];
|
||||
[self setConnection:connection];
|
||||
}
|
||||
else {
|
||||
RNCachedData *cache = [NSKeyedUnarchiver unarchiveObjectWithFile:[self cachePathForRequest:[self request]]];
|
||||
if (cache) {
|
||||
NSData *data = [cache data];
|
||||
NSURLResponse *response = [cache response];
|
||||
NSURLRequest *redirectRequest = [cache redirectRequest];
|
||||
if (redirectRequest) {
|
||||
[[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
|
||||
} else {
|
||||
|
||||
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // we handle caching ourselves.
|
||||
[[self client] URLProtocol:self didLoadData:data];
|
||||
[[self client] URLProtocolDidFinishLoading:self];
|
||||
}
|
||||
}
|
||||
else {
|
||||
[[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCannotConnectToHost userInfo:nil]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)stopLoading
|
||||
{
|
||||
[[self connection] cancel];
|
||||
}
|
||||
|
||||
// NSURLConnection delegates (generally we pass these on to our client)
|
||||
|
||||
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
|
||||
{
|
||||
// Thanks to Nick Dowell https://gist.github.com/1885821
|
||||
if (response != nil) {
|
||||
NSMutableURLRequest *redirectableRequest =
|
||||
#if WORKAROUND_MUTABLE_COPY_LEAK
|
||||
[request mutableCopyWorkaround];
|
||||
#else
|
||||
[request mutableCopy];
|
||||
#endif
|
||||
// We need to remove our header so we know to handle this request and cache it.
|
||||
// There are 3 requests in flight: the outside request, which we handled, the internal request,
|
||||
// which we marked with our header, and the redirectableRequest, which we're modifying here.
|
||||
// The redirectable request will cause a new outside request from the NSURLProtocolClient, which
|
||||
// must not be marked with our header.
|
||||
[redirectableRequest setValue:nil forHTTPHeaderField:RNCachingURLHeader];
|
||||
|
||||
NSString *cachePath = [self cachePathForRequest:[self request]];
|
||||
RNCachedData *cache = [RNCachedData new];
|
||||
[cache setResponse:response];
|
||||
[cache setData:[self data]];
|
||||
[cache setRedirectRequest:redirectableRequest];
|
||||
[NSKeyedArchiver archiveRootObject:cache toFile:cachePath];
|
||||
[[self client] URLProtocol:self wasRedirectedToRequest:redirectableRequest redirectResponse:response];
|
||||
return redirectableRequest;
|
||||
} else {
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
|
||||
{
|
||||
[[self client] URLProtocol:self didLoadData:data];
|
||||
[self appendData:data];
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
|
||||
{
|
||||
[[self client] URLProtocol:self didFailWithError:error];
|
||||
[self setConnection:nil];
|
||||
[self setData:nil];
|
||||
[self setResponse:nil];
|
||||
}
|
||||
|
||||
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
|
||||
{
|
||||
[self setResponse:response];
|
||||
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed]; // We cache ourselves.
|
||||
}
|
||||
|
||||
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
|
||||
{
|
||||
[[self client] URLProtocolDidFinishLoading:self];
|
||||
|
||||
NSString *cachePath = [self cachePathForRequest:[self request]];
|
||||
RNCachedData *cache = [RNCachedData new];
|
||||
[cache setResponse:[self response]];
|
||||
[cache setData:[self data]];
|
||||
[NSKeyedArchiver archiveRootObject:cache toFile:cachePath];
|
||||
|
||||
[self setConnection:nil];
|
||||
[self setData:nil];
|
||||
[self setResponse:nil];
|
||||
}
|
||||
|
||||
- (BOOL) useCache
|
||||
{
|
||||
BOOL reachable = (BOOL) [[Reachability reachabilityWithHostName:[[[self request] URL] host]] currentReachabilityStatus] != NotReachable;
|
||||
return !reachable;
|
||||
}
|
||||
|
||||
- (void)appendData:(NSData *)newData
|
||||
{
|
||||
if ([self data] == nil) {
|
||||
[self setData:[newData mutableCopy]];
|
||||
}
|
||||
else {
|
||||
[[self data] appendData:newData];
|
||||
}
|
||||
}
|
||||
|
||||
+ (NSSet *)supportedSchemes {
|
||||
NSSet *supportedSchemes;
|
||||
@synchronized(RNCachingSupportedSchemesMonitor)
|
||||
{
|
||||
supportedSchemes = RNCachingSupportedSchemes;
|
||||
}
|
||||
return supportedSchemes;
|
||||
}
|
||||
|
||||
+ (void)setSupportedSchemes:(NSSet *)supportedSchemes
|
||||
{
|
||||
@synchronized(RNCachingSupportedSchemesMonitor)
|
||||
{
|
||||
RNCachingSupportedSchemes = supportedSchemes;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static NSString *const kDataKey = @"data";
|
||||
static NSString *const kResponseKey = @"response";
|
||||
static NSString *const kRedirectRequestKey = @"redirectRequest";
|
||||
|
||||
@implementation RNCachedData
|
||||
@synthesize data = data_;
|
||||
@synthesize response = response_;
|
||||
@synthesize redirectRequest = redirectRequest_;
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)aCoder
|
||||
{
|
||||
[aCoder encodeObject:[self data] forKey:kDataKey];
|
||||
[aCoder encodeObject:[self response] forKey:kResponseKey];
|
||||
[aCoder encodeObject:[self redirectRequest] forKey:kRedirectRequestKey];
|
||||
}
|
||||
|
||||
- (id)initWithCoder:(NSCoder *)aDecoder
|
||||
{
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
[self setData:[aDecoder decodeObjectForKey:kDataKey]];
|
||||
[self setResponse:[aDecoder decodeObjectForKey:kResponseKey]];
|
||||
[self setRedirectRequest:[aDecoder decodeObjectForKey:kRedirectRequestKey]];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#if WORKAROUND_MUTABLE_COPY_LEAK
|
||||
@implementation NSURLRequest(MutableCopyWorkaround)
|
||||
|
||||
- (id) mutableCopyWorkaround {
|
||||
NSMutableURLRequest *mutableURLRequest = [[NSMutableURLRequest alloc] initWithURL:[self URL]
|
||||
cachePolicy:[self cachePolicy]
|
||||
timeoutInterval:[self timeoutInterval]];
|
||||
[mutableURLRequest setAllHTTPHeaderFields:[self allHTTPHeaderFields]];
|
||||
if ([self HTTPBodyStream]) {
|
||||
[mutableURLRequest setHTTPBodyStream:[self HTTPBodyStream]];
|
||||
} else {
|
||||
[mutableURLRequest setHTTPBody:[self HTTPBody]];
|
||||
}
|
||||
[mutableURLRequest setHTTPMethod:[self HTTPMethod]];
|
||||
|
||||
return mutableURLRequest;
|
||||
}
|
||||
|
||||
@end
|
||||
#endif
|
||||
Reference in New Issue
Block a user