From aaf6ea042e4a06cd20eaa7c340677b3bec7f375b Mon Sep 17 00:00:00 2001 From: Samuel Clay Date: Fri, 26 Sep 2014 17:38:35 -0700 Subject: [PATCH] Adding NSURLCache layer for caching of offline images. No more rewriting of image URLs. Thanks to @zacwest for the push on this. --- clients/ios/Classes/NewsBlurAppDelegate.m | 9 ++- .../ios/Classes/OriginalStoryViewController.m | 21 ++++-- .../ios/Classes/StoryDetailViewController.m | 31 ++++---- .../ios/Classes/offline/OfflineFetchImages.m | 2 +- .../ios/NewsBlur.xcodeproj/project.pbxproj | 6 ++ clients/ios/Other Sources/NBURLCache.h | 15 ++++ clients/ios/Other Sources/NBURLCache.m | 72 +++++++++++++++++++ 7 files changed, 132 insertions(+), 24 deletions(-) create mode 100644 clients/ios/Other Sources/NBURLCache.h create mode 100644 clients/ios/Other Sources/NBURLCache.m diff --git a/clients/ios/Classes/NewsBlurAppDelegate.m b/clients/ios/Classes/NewsBlurAppDelegate.m index 07f1906c2..affc571f2 100644 --- a/clients/ios/Classes/NewsBlurAppDelegate.m +++ b/clients/ios/Classes/NewsBlurAppDelegate.m @@ -55,11 +55,12 @@ #import "NSString+HTML.h" #import "UIView+ViewController.h" #import "UIViewController+OSKUtilities.h" +#import "NBURLCache.h" #import @implementation NewsBlurAppDelegate -#define CURRENT_DB_VERSION 31 +#define CURRENT_DB_VERSION 32 @synthesize window; @@ -209,7 +210,11 @@ cachedFavicons = [[TMCache alloc] initWithName:@"NBFavicons"]; cachedStoryImages = [[TMCache alloc] initWithName:@"NBStoryImages"]; - + + NBURLCache *urlCache = [[NBURLCache alloc] init]; + [NSURLCache setSharedURLCache:urlCache]; + [[NSURLCache sharedURLCache] removeAllCachedResponses]; + return YES; } diff --git a/clients/ios/Classes/OriginalStoryViewController.m b/clients/ios/Classes/OriginalStoryViewController.m index 28422a1ef..3f23c3aa1 100644 --- a/clients/ios/Classes/OriginalStoryViewController.m +++ b/clients/ios/Classes/OriginalStoryViewController.m @@ -279,6 +279,8 @@ { [UIApplication sharedApplication].networkActivityIndicatorVisible = NO; +// if (error.code == 102 && [error.domain isEqual:@"WebKitErrorDomain"]) { } + // User clicking on another link before the page loads is OK. if ([error code] != NSURLErrorCancelled) { [self informError:error]; @@ -293,18 +295,25 @@ } - (IBAction)loadAddress:(id)sender { - if (!activeUrl) { - activeUrl = [appDelegate.activeOriginalStoryURL absoluteString]; - } + activeUrl = [appDelegate.activeOriginalStoryURL absoluteString]; NSString* urlString = activeUrl; NSURL* url = [NSURL URLWithString:urlString]; +// if ([urlString containsString:@"story_images"]) { +// NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); +// NSString *storyImagesDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"story_images"]; +// +// urlString = [urlString substringFromIndex:NSMaxRange([urlString +// rangeOfString:@"story_images/"])]; +// NSString *path = [storyImagesDirectory stringByAppendingPathComponent:urlString]; +// url = [NSURL fileURLWithPath:path]; +// } if (!url.scheme) { NSString* modifiedURLString = [NSString stringWithFormat:@"%@", urlString]; url = [NSURL URLWithString:modifiedURLString]; } - if ([self.webView isLoading]) { - [self.webView stopLoading]; - } +// if ([self.webView isLoading]) { +// [self.webView stopLoading]; +// } NSURLRequest* request = [NSURLRequest requestWithURL:url]; [self.webView loadRequest:request]; titleView.text = @"Loading..."; diff --git a/clients/ios/Classes/StoryDetailViewController.m b/clients/ios/Classes/StoryDetailViewController.m index 068669eeb..4ad9040bf 100644 --- a/clients/ios/Classes/StoryDetailViewController.m +++ b/clients/ios/Classes/StoryDetailViewController.m @@ -272,20 +272,20 @@ contentWidthClass, (int)floorf(CGRectGetWidth(self.view.frame))]; // Replace image urls that are locally cached, even when online - NSString *storyHash = [self.activeStory objectForKey:@"story_hash"]; - NSArray *imageUrls = [appDelegate.activeCachedImages objectForKey:storyHash]; - if (imageUrls) { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); - NSString *storyImagesDirectory = [[paths objectAtIndex:0] - stringByAppendingPathComponent:@"story_images"]; - for (NSString *imageUrl in imageUrls) { - NSString *cachedImage = [storyImagesDirectory - stringByAppendingPathComponent:[Utilities md5:imageUrl]]; - storyContent = [storyContent - stringByReplacingOccurrencesOfString:imageUrl - withString:cachedImage]; - } - } +// NSString *storyHash = [self.activeStory objectForKey:@"story_hash"]; +// NSArray *imageUrls = [appDelegate.activeCachedImages objectForKey:storyHash]; +// if (imageUrls) { +// NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); +// NSString *storyImagesDirectory = [[paths objectAtIndex:0] +// stringByAppendingPathComponent:@"story_images"]; +// for (NSString *imageUrl in imageUrls) { +// NSString *cachedImage = [storyImagesDirectory +// stringByAppendingPathComponent:[Utilities md5:imageUrl]]; +// storyContent = [storyContent +// stringByReplacingOccurrencesOfString:imageUrl +// withString:cachedImage]; +// } +// } NSString *riverClass = (appDelegate.storiesCollection.isRiverView || appDelegate.storiesCollection.isSocialView || @@ -1552,7 +1552,8 @@ shouldStartLoadWithRequest:(NSURLRequest *)request actionSheetViewImageIndex = [actions addButtonWithTitle:@"View and zoom"]; actionSheetCopyImageIndex = [actions addButtonWithTitle:@"Copy image"]; actionSheetSaveImageIndex = [actions addButtonWithTitle:@"Save to camera roll"]; - [actions showFromRect:CGRectMake(pt.x, pt.y, 1, 1) inView:appDelegate.storyPageControl.view animated:YES]; + [actions showFromRect:CGRectMake(pt.x, pt.y, 1, 1) + inView:appDelegate.storyPageControl.view animated:YES]; // [actions showInView:appDelegate.storyPageControl.view]; } diff --git a/clients/ios/Classes/offline/OfflineFetchImages.m b/clients/ios/Classes/offline/OfflineFetchImages.m index d289523aa..465e756ce 100644 --- a/clients/ios/Classes/offline/OfflineFetchImages.m +++ b/clients/ios/Classes/offline/OfflineFetchImages.m @@ -168,7 +168,7 @@ NSFileManager *fileManager = [NSFileManager defaultManager]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); NSString *cacheDirectory = [[paths objectAtIndex:0] stringByAppendingPathComponent:@"story_images"]; - NSString *fullPath = [cacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@", md5Url]]; + NSString *fullPath = [cacheDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", md5Url, [[[request originalURL] absoluteString] pathExtension]]]; [fileManager createFileAtPath:fullPath contents:responseData attributes:nil]; } else { diff --git a/clients/ios/NewsBlur.xcodeproj/project.pbxproj b/clients/ios/NewsBlur.xcodeproj/project.pbxproj index fea58b912..f4d6fd914 100755 --- a/clients/ios/NewsBlur.xcodeproj/project.pbxproj +++ b/clients/ios/NewsBlur.xcodeproj/project.pbxproj @@ -675,6 +675,7 @@ FFF1E4CB17750D2C00BF59D3 /* menu_icn_preferences.png in Resources */ = {isa = PBXBuildFile; fileRef = FFF1E4C917750D2C00BF59D3 /* menu_icn_preferences.png */; }; FFF1E4CC17750D2C00BF59D3 /* menu_icn_preferences@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = FFF1E4CA17750D2C00BF59D3 /* menu_icn_preferences@2x.png */; }; FFFC608517165A1D00DC22E2 /* THCircularProgressView.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFC60831716578E00DC22E2 /* THCircularProgressView.m */; }; + FFFF683D19D628000081904A /* NBURLCache.m in Sources */ = {isa = PBXBuildFile; fileRef = FFFF683C19D628000081904A /* NBURLCache.m */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -1623,6 +1624,8 @@ FFF1E4CA17750D2C00BF59D3 /* menu_icn_preferences@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "menu_icn_preferences@2x.png"; sourceTree = ""; }; FFFC60821716578E00DC22E2 /* THCircularProgressView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = THCircularProgressView.h; sourceTree = ""; }; FFFC60831716578E00DC22E2 /* THCircularProgressView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = THCircularProgressView.m; sourceTree = ""; }; + FFFF683B19D628000081904A /* NBURLCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = NBURLCache.h; path = "Other Sources/NBURLCache.h"; sourceTree = ""; }; + FFFF683C19D628000081904A /* NBURLCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = NBURLCache.m; path = "Other Sources/NBURLCache.m"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -1742,6 +1745,8 @@ FF1F13D718AAC97900FDA816 /* UIImage+Resize.m */, FFA0483F19CA5F5B00618DC4 /* UIWebView+Offsets.h */, FFA0484019CA5F5B00618DC4 /* UIWebView+Offsets.m */, + FFFF683B19D628000081904A /* NBURLCache.h */, + FFFF683C19D628000081904A /* NBURLCache.m */, ); name = "Other Sources"; sourceTree = ""; @@ -3498,6 +3503,7 @@ 1D3623260D0F684500981E51 /* NewsBlurAppDelegate.m in Sources */, FFA047DA19CA54A800618DC4 /* NSMutableURLRequest+OSKUtilities.m in Sources */, 28D7ACF80DDB3853001CB0EB /* NewsBlurViewController.m in Sources */, + FFFF683D19D628000081904A /* NBURLCache.m in Sources */, FFA0481619CA54A800618DC4 /* OSKPagedHorizontalLayout.m in Sources */, 78FC34F711CA94900055C312 /* NSObject+SBJSON.m in Sources */, FFA0482119CA54A800618DC4 /* OSKSaveToCameraRollActivity.m in Sources */, diff --git a/clients/ios/Other Sources/NBURLCache.h b/clients/ios/Other Sources/NBURLCache.h new file mode 100644 index 000000000..9a6921c78 --- /dev/null +++ b/clients/ios/Other Sources/NBURLCache.h @@ -0,0 +1,15 @@ +// +// NBURLCache.h +// NewsBlur +// +// Created by Samuel Clay on 9/26/14. +// Copyright (c) 2014 NewsBlur. All rights reserved. +// + +#import + +@interface NBURLCache : NSURLCache { + NSMutableDictionary *cachedResponses; +} + +@end diff --git a/clients/ios/Other Sources/NBURLCache.m b/clients/ios/Other Sources/NBURLCache.m new file mode 100644 index 000000000..3101be56f --- /dev/null +++ b/clients/ios/Other Sources/NBURLCache.m @@ -0,0 +1,72 @@ +// +// NBURLCache.m +// NewsBlur +// +// Created by Samuel Clay on 9/26/14. +// Copyright (c) 2014 NewsBlur. All rights reserved. +// + +#import "NBURLCache.h" +#import "Utilities.h" + +@implementation NBURLCache + +- (NSString *)substitutePath:(NSString *)pathString { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); + NSString *storyImagesDirectory = [[paths objectAtIndex:0] + stringByAppendingPathComponent:@"story_images"]; + NSString *cachedImage = [[storyImagesDirectory + stringByAppendingPathComponent:[Utilities md5:pathString]] stringByAppendingPathExtension:[pathString pathExtension]]; + return cachedImage; +} + +- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request +{ + // Get the path for the request + NSString *pathString = [[request URL] absoluteString]; + + // See if we have a substitution file for this path + NSString *substitutionFileName = [self substitutePath:pathString]; + BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:substitutionFileName]; + if (!substitutionFileName || !exists) + { + NSLog(@"No cache found: %@ / %@", pathString, substitutionFileName); + // No substitution file, return the default cache response + return [super cachedResponseForRequest:request]; + } + + // If we've already created a cache entry for this path, then return it. + NSCachedURLResponse *cachedResponse = [cachedResponses objectForKey:pathString]; + if (cachedResponse) + { + NSLog(@"Memory cached: %@", pathString); +// return cachedResponse; + } + + // Get the path to the substitution file + NSString *substitutionFilePath = substitutionFileName; + + // Load the data + NSData *data = [NSData dataWithContentsOfFile:substitutionFilePath]; + + // Create the cacheable response + NSURLResponse *response = [[NSURLResponse alloc] + initWithURL:[request URL] + MIMEType:nil + expectedContentLength:[data length] + textEncodingName:nil]; + cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response + data:data]; + + NSLog(@"Got cached response: %@ / %@", pathString, substitutionFileName); + // Add it to our cache dictionary for subsequent responses + if (!cachedResponses) + { + cachedResponses = [[NSMutableDictionary alloc] init]; + } + [cachedResponses setObject:cachedResponse forKey:pathString]; + + return cachedResponse; +} + +@end