Bgerstle has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/220527

Change subject: centralized image caching & retrieval
......................................................................

centralized image caching & retrieval

Project changes:
- Added SDWebImage fork as a submodule, which means you now need to run
  `git submodule update --init --recursive` to grab the files needed by
  the project (which CocoaPods integrates into the project like any
  other Pod)...
- Luckily `make pod` will now do this for you
Code changes:
- Wrote WMFImageController to manage fetching, caching, and downloading
  images (on top of SDWebImageManager)
- Rewrote WMFArticleImageProtocol to use WMFImageController
What works right now:
- Lead & article images in the webview (face detection should also
  work, but can't search to find an article with a face)
- Pull to refresh loading from the cache
What doesn't work:
- Native images (not using old MWKDataStore image storage)
  - As a result of this, saved pages is also broken until migration is
    implemented & WMFImageController is integrated
- Keeping images for saved pages: need to add lookup during disk
  cleanup, pending extension of SDWebImage in next ticket (T101527)

Bug: T95350
Change-Id: I98037e1637fd8d6534a570480edc0ab1556fb44b
---
M Wikipedia.xcodeproj/project.pbxproj
A Wikipedia/Images/SDWebImageManager+PromiseKit.swift
A Wikipedia/Images/WMFImageController.swift
A Wikipedia/Networking/Cancellable.swift
M Wikipedia/Protocols/WMFArticleImageProtocol.m
M Wikipedia/Wikipedia-Bridging-Header.h
A Wikipedia/mw-utils/SwiftUtilities.swift
7 files changed, 386 insertions(+), 148 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/apps/ios/wikipedia 
refs/changes/27/220527/10

diff --git a/Wikipedia.xcodeproj/project.pbxproj 
b/Wikipedia.xcodeproj/project.pbxproj
index 30633d4..64df850 100644
--- a/Wikipedia.xcodeproj/project.pbxproj
+++ b/Wikipedia.xcodeproj/project.pbxproj
@@ -321,6 +321,10 @@
                BCB848781AAAABF80077EC24 /* WMFRoundingUtilities.c in Sources 
*/ = {isa = PBXBuildFile; fileRef = BCB848771AAAABF80077EC24 /* 
WMFRoundingUtilities.c */; };
                BCB8487B1AAAADF90077EC24 /* WMFRoundingUtilitiesTests.m in 
Sources */ = {isa = PBXBuildFile; fileRef = BCB8487A1AAAADF90077EC24 /* 
WMFRoundingUtilitiesTests.m */; };
                BCB848831AAE0C5C0077EC24 /* WMFImageGalleryCollectionViewCell.m 
in Sources */ = {isa = PBXBuildFile; fileRef = 08D631F91A69B8CD00D87AD0 /* 
WMFImageGalleryCollectionViewCell.m */; };
+               BCBDC8821B38E414003A6D17 /* SDWebImageManager+PromiseKit.swift 
in Sources */ = {isa = PBXBuildFile; fileRef = BCBDC8811B38E414003A6D17 /* 
SDWebImageManager+PromiseKit.swift */; };
+               BCBDC8841B38E441003A6D17 /* WMFImageController.swift in Sources 
*/ = {isa = PBXBuildFile; fileRef = BCBDC8831B38E441003A6D17 /* 
WMFImageController.swift */; };
+               BCBDC88C1B3A0715003A6D17 /* Cancellable.swift in Sources */ = 
{isa = PBXBuildFile; fileRef = BCBDC88B1B3A0715003A6D17 /* Cancellable.swift 
*/; };
+               BCBDC88E1B3A42E7003A6D17 /* SwiftUtilities.swift in Sources */ 
= {isa = PBXBuildFile; fileRef = BCBDC88D1B3A42E7003A6D17 /* 
SwiftUtilities.swift */; };
                BCC185D81A9E5628005378F8 /* UILabel+WMFStyling.m in Sources */ 
= {isa = PBXBuildFile; fileRef = BCC185D71A9E5628005378F8 /* 
UILabel+WMFStyling.m */; };
                BCC185E01A9EC836005378F8 /* UIButton+FrameUtils.m in Sources */ 
= {isa = PBXBuildFile; fileRef = BCC185DF1A9EC836005378F8 /* 
UIButton+FrameUtils.m */; };
                BCC185E81A9FA498005378F8 /* 
UICollectionViewFlowLayout+AttributeUtils.m in Sources */ = {isa = 
PBXBuildFile; fileRef = BCC185E71A9FA498005378F8 /* 
UICollectionViewFlowLayout+AttributeUtils.m */; };
@@ -968,6 +972,10 @@
                BCB848771AAAABF80077EC24 /* WMFRoundingUtilities.c */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = 
WMFRoundingUtilities.c; sourceTree = "<group>"; };
                BCB8487A1AAAADF90077EC24 /* WMFRoundingUtilitiesTests.m */ = 
{isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.c.objc; path = WMFRoundingUtilitiesTests.m; sourceTree = "<group>"; 
};
                BCB848811AAE06420077EC24 /* ImageGallerySpecs */ = {isa = 
PBXFileReference; explicitFileType = text.script.sh; fileEncoding = 4; name = 
ImageGallerySpecs; path = "Image Gallery/ImageGallerySpecs"; sourceTree = 
"<group>"; };
+               BCBDC8811B38E414003A6D17 /* SDWebImageManager+PromiseKit.swift 
*/ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = 
sourcecode.swift; name = "SDWebImageManager+PromiseKit.swift"; path = 
"Images/SDWebImageManager+PromiseKit.swift"; sourceTree = "<group>"; };
+               BCBDC8831B38E441003A6D17 /* WMFImageController.swift */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; 
name = WMFImageController.swift; path = Images/WMFImageController.swift; 
sourceTree = "<group>"; };
+               BCBDC88B1B3A0715003A6D17 /* Cancellable.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= Cancellable.swift; sourceTree = "<group>"; };
+               BCBDC88D1B3A42E7003A6D17 /* SwiftUtilities.swift */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path 
= SwiftUtilities.swift; sourceTree = "<group>"; };
                BCBDE0AB1AA76EAC006BD29A /* WMFImageURLParsingTests.m */ = {isa 
= PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; 
path = WMFImageURLParsingTests.m; sourceTree = "<group>"; };
                BCC185D61A9E5628005378F8 /* UILabel+WMFStyling.h */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = 
"UILabel+WMFStyling.h"; sourceTree = "<group>"; };
                BCC185D71A9E5628005378F8 /* UILabel+WMFStyling.m */ = {isa = 
PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path 
= "UILabel+WMFStyling.m"; sourceTree = "<group>"; };
@@ -1529,6 +1537,7 @@
                                BC955BCE1A82C2FA000EF9E4 /* 
AFHTTPRequestOperationManager+WMFConfig.m */,
                                BC50C37D1A83C784006DC7AF /* 
WMFNetworkUtilities.h */,
                                BC50C37E1A83C784006DC7AF /* 
WMFNetworkUtilities.m */,
+                               BCBDC88B1B3A0715003A6D17 /* Cancellable.swift 
*/,
                        );
                        path = Networking;
                        sourceTree = "<group>";
@@ -2129,6 +2138,15 @@
                        path = Categories;
                        sourceTree = "<group>";
                };
+               BC628C791B389E2B00B3F85C /* Images */ = {
+                       isa = PBXGroup;
+                       children = (
+                               BCBDC8831B38E441003A6D17 /* 
WMFImageController.swift */,
+                               BCBDC8811B38E414003A6D17 /* 
SDWebImageManager+PromiseKit.swift */,
+                       );
+                       name = Images;
+                       sourceTree = "<group>";
+               };
                BC69C3101AB0C16B0090B039 /* View Model */ = {
                        isa = PBXGroup;
                        children = (
@@ -2462,6 +2480,7 @@
                                BC092B971B18E8AF00093C59 /* 
NSString+WMFPageUtilities.h */,
                                BC092B951B18E89200093C59 /* 
NSString+WMFPageUtilities.m */,
                                BC7E4A491B34A26A00EECD8B /* WMFLogging.h */,
+                               BCBDC88D1B3A42E7003A6D17 /* 
SwiftUtilities.swift */,
                        );
                        path = "mw-utils";
                        sourceTree = "<group>";
@@ -2564,6 +2583,7 @@
                                D4B0ADFF19365F4600F0AC90 /* EventLogging */,
                                0442F57C1900718600F55DF9 /* Fonts */,
                                0493C2C91952373100EBB973 /* Housekeeping */,
+                               BC628C791B389E2B00B3F85C /* Images */,
                                0463639518A844380049EE4F /* Keychain */,
                                0487041519F824D700B7D307 /* Networking */,
                                04616DF71AE7060C00815BCE /* Protocols */,
@@ -3160,6 +3180,7 @@
                                BC23759A1AB78D8A00B0BAA8 /* 
NSParagraphStyle+WMFNaturalAlignmentStyle.m in Sources */,
                                04090A3B187FB7D000577EDF /* UIView+Debugging.m 
in Sources */,
                                BCC185D81A9E5628005378F8 /* 
UILabel+WMFStyling.m in Sources */,
+                               BCBDC8841B38E441003A6D17 /* 
WMFImageController.swift in Sources */,
                                0EFB0F241B31EE2D00D05C08 /* Saved.m in Sources 
*/,
                                BCB669B11A83F6C400C7B1FE /* 
MWKRecentSearchList.m in Sources */,
                                04DD89B118BFE63A00DD5DAD /* 
PreviewAndSaveViewController.m in Sources */,
@@ -3236,6 +3257,7 @@
                                BCB58F671A8AA22200465627 /* 
MWKLicense+ToGlyph.m in Sources */,
                                0EFB0F191B31EE2D00D05C08 /* Article.m in 
Sources */,
                                04C43AAE18344131006C643B /* 
CommunicationBridge.m in Sources */,
+                               BCBDC8821B38E414003A6D17 /* 
SDWebImageManager+PromiseKit.swift in Sources */,
                                045AB8C31B1E15D9002839D7 /* NSURL+Extras.m in 
Sources */,
                                04224501197F5E09005DD0BF /* BulletedLabel.m in 
Sources */,
                                C979727D1A731F2D00C6ED7A /* 
WMFShareOptionsView.m in Sources */,
@@ -3272,6 +3294,7 @@
                                04F39590186CF80100B0D6FC /* TOCViewController.m 
in Sources */,
                                BCE24FDF1B0CF0C7003F054B /* 
LegacyPhoneGapDataMigrator.m in Sources */,
                                042950D41A9D3BA7009BE784 /* 
UIColor+WMFHexColor.m in Sources */,
+                               BCBDC88C1B3A0715003A6D17 /* Cancellable.swift 
in Sources */,
                                0439317619FB092600386E8F /* 
UIWebView+LoadAssetsHtml.m in Sources */,
                                04B91AA718E34BBC00FFAA1C /* 
UIView+TemporaryAnimatedXF.m in Sources */,
                                04224500197F5E09005DD0BF /* AbuseFilterAlert.m 
in Sources */,
@@ -3301,6 +3324,7 @@
                                BCE24FDD1B0CF0C7003F054B /* 
LegacyCoreDataMigrator.m in Sources */,
                                04478633185145090050563B /* 
HistoryViewController.m in Sources */,
                                BCB669B21A83F6C400C7B1FE /* MWKImageInfo.m in 
Sources */,
+                               BCBDC88E1B3A42E7003A6D17 /* 
SwiftUtilities.swift in Sources */,
                                BCB848781AAAABF80077EC24 /* 
WMFRoundingUtilities.c in Sources */,
                                D4B0AE051936604700F0AC90 /* EditFunnel.m in 
Sources */,
                                0487048319F8262600B7D307 /* FetcherBase.m in 
Sources */,
diff --git a/Wikipedia/Images/SDWebImageManager+PromiseKit.swift 
b/Wikipedia/Images/SDWebImageManager+PromiseKit.swift
new file mode 100644
index 0000000..09e60ed
--- /dev/null
+++ b/Wikipedia/Images/SDWebImageManager+PromiseKit.swift
@@ -0,0 +1,49 @@
+//
+//  SDWebImageManager+PromiseKit.swift
+//  Wikipedia
+//
+//  Created by Brian Gerstle on 6/22/15.
+//  Copyright (c) 2015 Wikimedia Foundation. All rights reserved.
+//
+
+import Foundation
+
+public typealias ImageDownload = (image: UIImage, data: NSData?)
+
+public class ImageOperationWrapper<T> : Promise<T>, Cancellable {
+    var operation: SDWebImageOperation!
+
+    // need our own special version of "defer" because Promise is a class, not 
a protocol
+    public class func wrapper() -> (ImageOperationWrapper<T>, (T)->Void, 
(NSError)->Void) {
+        var sealant: Sealant<T>!
+        var wrapper = ImageOperationWrapper<T>(sealant: { sealant = $0 })
+        return (wrapper, sealant.resolve, sealant.resolve)
+    }
+
+    // `wrapper()` is provided as a convenience (similar to `Promise.defer()`)
+    public init(sealant: (Sealant<T>)->Void) {
+        super.init(sealant: sealant)
+    }
+
+    func cancel() -> Void {
+        // cancel underlying operation (idempotent)
+        operation.cancel()
+    }
+}
+
+extension SDWebImageManager {
+    func promisedImageWithURL(URL: NSURL, options: SDWebImageOptions) -> 
ImageOperationWrapper<ImageDownload> {
+        let (wrapper, fulfill, reject) = 
ImageOperationWrapper<ImageDownload>.wrapper()
+        wrapper.operation = self.downloadImageAndDataWithURL(URL,
+                                                             options: options,
+                                                             progress: nil)
+        { img, data, err, cacheType, finished, imageURL in
+                if finished && err == nil {
+                    fulfill((img, data))
+                } else {
+                    reject(err)
+                }
+        }
+        return wrapper
+    }
+}
diff --git a/Wikipedia/Images/WMFImageController.swift 
b/Wikipedia/Images/WMFImageController.swift
new file mode 100644
index 0000000..5316e5c
--- /dev/null
+++ b/Wikipedia/Images/WMFImageController.swift
@@ -0,0 +1,110 @@
+//
+//  WMFImageController.swift
+//  Wikipedia
+//
+//  Created by Brian Gerstle on 6/22/15.
+//  Copyright (c) 2015 Wikimedia Foundation. All rights reserved.
+//
+
+import Foundation
+
+@objc
+public class WMFImageController : NSObject {
+
+    /// MARK: Initialization
+
+    private static let _sharedInstance = {
+        return WMFImageController(manager: SDWebImageManager.sharedManager())
+    }()
+
+    class func sharedInstance() -> WMFImageController {
+        return _sharedInstance
+    }
+
+    private let imageManager: SDWebImageManager
+
+    private lazy var cancellingQueue: dispatch_queue_t = {
+        
dispatch_queue_create("org.wikimedia.wikipedia.wmfimagecontroller.\(address(self))",
+                              DISPATCH_QUEUE_CONCURRENT)
+    }()
+
+    private lazy var cancellables: NSMapTable = {
+        NSMapTable.strongToWeakObjectsMapTable()
+    }()
+
+    public init(manager: SDWebImageManager) {
+        self.imageManager = manager;
+    }
+
+    public convenience init(sharedCacheFromController otherController: 
WMFImageController) {
+        self.init(manager: SDWebImageManager(downloader: 
SDWebImageDownloader(),
+                                             cache: 
otherController.imageManager.imageCache))
+    }
+
+    /// MARK: Fetching
+
+    /// Retrieve the data and uncompressed image for `url`.
+    public func fetchImageWithURL(url: NSURL) -> Promise<ImageDownload> {
+        let promise = imageManager.promisedImageWithURL(url, options: 
SDWebImageOptions.allZeros)
+        self.addCancellableForURL(promise, url: url)
+        return promise
+    }
+
+    public func isDownloadingImageWithURL(url: NSURL) -> Bool {
+        return imageManager.imageDownloader.isDownloadingImageAtURL(url)
+    }
+
+    // MARK: Caching
+
+    public func dataForImageWithURL(url: NSURL) -> NSData? {
+        return 
imageManager.imageCache.dataFromDiskCacheForKey(url.absoluteString!)
+    }
+
+    public func cachedImageWithURL(url: NSURL) -> UIImage? {
+        return 
imageManager.imageCache.imageFromDiskCacheForKey(url.absoluteString!)
+    }
+
+    public func cancelFetchForURL(url: NSURL) {
+        weak var wself = self;
+        dispatch_barrier_async(self.cancellingQueue) {
+            let sself = wself
+            if let cancellable = 
sself?.cancellables.objectForKey(url.absoluteString!) as? 
WrapperObject<Cancellable> {
+                sself?.cancellables.removeObjectForKey(url.absoluteString!)
+                cancellable.value.cancel()
+            }
+        }
+    }
+
+    /// MARK: Private
+
+    private func addCancellableForURL(cancellable: Cancellable, url: NSURL) {
+        weak var wself = self;
+        dispatch_async(self.cancellingQueue) {
+            let sself = wself
+            sself?.cancellables.setObject(WrapperObject(value: cancellable), 
forKey: url.absoluteString!)
+        }
+    }
+}
+
+/// MARK: Objective-C Bridge
+
+/// Class which packages up the Swift tuple response from 
`fetchImageWithURL(url:)`
+@objc class WMFImageDownload : NSObject {
+    var image: UIImage
+    var data: NSData?
+
+    init(image: UIImage, data: NSData?) {
+        self.image = image
+        self.data = data
+    }
+}
+
+extension WMFImageController {
+    /// Objective-C compatible `AnyPromise` which resolves to 
`WMFImageDownload`.
+    public func fetchImageWithURL(url: NSURL) -> AnyPromise {
+        let adaptedPromise: Promise<WMFImageDownload> = 
fetchImageWithURL(url).thenInBackground() { img, data in
+            return WMFImageDownload(image: img, data: data)
+        }
+        return AnyPromise(bound: adaptedPromise)
+    }
+}
diff --git a/Wikipedia/Networking/Cancellable.swift 
b/Wikipedia/Networking/Cancellable.swift
new file mode 100644
index 0000000..eefac05
--- /dev/null
+++ b/Wikipedia/Networking/Cancellable.swift
@@ -0,0 +1,13 @@
+//
+//  Cancellable.swift
+//  Wikipedia
+//
+//  Created by Brian Gerstle on 6/23/15.
+//  Copyright (c) 2015 Wikimedia Foundation. All rights reserved.
+//
+
+import Foundation
+
+protocol Cancellable {
+    func cancel() -> Void
+}
diff --git a/Wikipedia/Protocols/WMFArticleImageProtocol.m 
b/Wikipedia/Protocols/WMFArticleImageProtocol.m
index fd8582b..83e2b0e 100644
--- a/Wikipedia/Protocols/WMFArticleImageProtocol.m
+++ b/Wikipedia/Protocols/WMFArticleImageProtocol.m
@@ -5,52 +5,112 @@
 #import "NSURL+WMFRest.h"
 #import "SessionSingleton.h"
 #import "NSString+Extras.h"
+#import "Wikipedia-Swift.h"
+#import "PromiseKit.h"
+
+#pragma mark - Logging Config
+
+// Set the level for logs in this file
+#undef LOG_LEVEL_DEF
+#define LOG_LEVEL_DEF WMFArticleImageProtocolLogLevel
+static const int WMFArticleImageProtocolLogLevel = DDLogLevelInfo;
+
+#pragma mark - Typedefs
+
+typedef void (^ WMFImageDownloadBlock)(WMFImageDownload* download);
+
+#pragma mark - Constants
 
 NSString* const WMFArticleImageSectionImageRetrievedNotification = 
@"WMFSectionImageRetrieved";
+static NSString* const WMFArticleImageProtocolHost               = 
@"upload.wikimedia.org";
 
-// We need to set a property on the request to prevent infinite loops due to 
handling "http(s)" requests.
-// See: http://www.raywenderlich.com/59982/nsurlprotocol-tutorial
-static NSString* const WMFArticleImageProtocolAlreadyHandled = 
@"WMFArticleImageProtocolAlreadyHandled";
+#pragma mark - Handling Checks
 
-static NSString* const WMFArticleImageProtocolHost = @"upload.wikimedia.org";
-
-__attribute__((constructor)) static void WMFRegisterArticleImageProtocol() {
-    [NSURLProtocol registerClass:[WMFArticleImageProtocol class]];
+static inline BOOL isSchemeHandled(NSURLRequest* request) {
+    if ([[[request URL] scheme] hasPrefix:@"http"]) {
+        return YES;
+    } else {
+        DDLogVerbose(@"Skipping non-HTTP request: %@", request);
+        return NO;
+    }
 }
 
-@interface WMFArticleImageProtocol () <NSURLConnectionDelegate>
-@property (nonatomic, strong) NSURLConnection* connection;
-@property (nonatomic, strong) NSMutableData* mutableImageData;
-@property (nonatomic, strong) NSURLResponse* response;
-@end
+static NSArray* mimeTypesToCache() {
+    static NSArray* types = nil;
+    static dispatch_once_t once;
+    dispatch_once(&once, ^{
+        types = @[@"image/jpeg", @"image/png", @"image/gif"];
+    });
+    return types;
+}
+
+static inline BOOL isImageMIMEType(NSURLRequest* request) {
+    NSString* extension = request.URL.pathExtension;
+    // HAX: the path extension of a request URL is not always a reliable way 
to detect its (expected) response MIME type
+    // maybe we should check request content-type header? would need to set 
Accept: application/json for API requests
+    if ([mimeTypesToCache() containsObject:[extension 
wmf_mimeTypeForExtension]]) {
+        return YES;
+    } else {
+        DDLogVerbose(@"Skipping request %@ with MIME type: %@", request, 
extension);
+        return NO;
+    }
+}
+
+static BOOL imagePlaceholderExists(NSURLRequest* request) {
+    NSArray* variants =
+        [[SessionSingleton sharedInstance].currentArticle.images 
imageSizeVariants:request.URL.absoluteString];
+    if (variants.count != 0) {
+        return YES;
+    } else {
+        DDLogVerbose(@"Skipping request without image variant placeholder: 
%@", request);
+        return NO;
+    }
+}
+
+static inline BOOL isNotDownloadingImageFromURL(NSURLRequest* request) {
+    if ([[WMFImageController sharedInstance] 
isDownloadingImageWithURL:request.URL]) {
+        DDLogVerbose(@"Skipping redundant download for image at %@", request);
+        return NO;
+    } else {
+        return YES;
+    }
+}
+
+static inline BOOL isFromWikipediaImageHost(NSURLRequest* request) {
+    if ([request.URL.host 
wmf_caseInsensitiveContainsString:WMFArticleImageProtocolHost]) {
+        return YES;
+    } else {
+        DDLogVerbose(@"Skipping request with host not matching %@: %@", 
WMFArticleImageProtocolHost, request);
+        return NO;
+    }
+}
 
 @implementation WMFArticleImageProtocol
 
+#pragma mark - Registration & Initialization
+
++ (void)load {
+    [NSURLProtocol registerClass:self];
+}
+
 + (BOOL)canInitWithRequest:(NSURLRequest*)request {
-    if (
-        // Has 'http' or 'https' scheme and 'upload.wikimedia.org' host.
-        ![[request URL] wmf_conformsToAnyOfSchemes:[self schemesToExamine] 
andHasHost:WMFArticleImageProtocolHost] ||
-
-        // Prevent multiple 'startLoading' calls for a given resource.
-        [NSURLProtocol propertyForKey:WMFArticleImageProtocolAlreadyHandled 
inRequest:request] ||
-
-        // Check that extension is one we are interested in.
-        ![self isFileExtensionRerouted:request.URL.pathExtension] ||
-
-        // Only interested if image (or a size variant of image) has a data 
store record.
-        // (we make placeholder records when the html is received and parsed)
-        ![self imageVariantPlaceHolderRecordFoundForRequest:request]
-        ) {
-        return NO;
-    }
-
-    return YES;
+    return isSchemeHandled(request)
+           && isImageMIMEType(request)
+           && isFromWikipediaImageHost(request)
+           // BG: Do these checks last, as they're the most expensive
+           && imagePlaceholderExists(request)
+           && isNotDownloadingImageFromURL(request);
 }
 
-+ (BOOL)imageVariantPlaceHolderRecordFoundForRequest:(NSURLRequest*)request {
-    NSArray* variants = [self.article.images 
imageSizeVariants:request.URL.absoluteString];
-    return (!variants || (variants.count == 0)) ? NO : YES;
++ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request {
+    return [[NSURLRequest alloc] initWithURL:request.URL];
 }
+
++ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)a toRequest:(NSURLRequest*)b {
+    return [super requestIsCacheEquivalent:a toRequest:b];
+}
+
+#pragma mark - Getters
 
 + (MWKArticle*)article {
     return [SessionSingleton sharedInstance].currentArticle;
@@ -60,132 +120,71 @@
     return [WMFArticleImageProtocol article];
 }
 
-+ (NSArray*)schemesToExamine {
-    static NSArray* schemes = nil;
-    static dispatch_once_t once;
-    dispatch_once(&once, ^{
-        schemes = @[@"https", @"http"];
-    });
-    return schemes;
-}
+#pragma mark - NSURLProtocol
 
-+ (NSArray*)mimeTypesToCache {
-    static NSArray* types = nil;
-    static dispatch_once_t once;
-    dispatch_once(&once, ^{
-        types = @[@"image/jpeg", @"image/png", @"image/gif"];
-    });
-    return types;
-}
-
-+ (BOOL)isFileExtensionRerouted:(NSString*)extension {
-    return [self.mimeTypesToCache containsObject:[extension 
wmf_mimeTypeForExtension]];
-}
-
-+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest*)request {
-    return request;
-}
-
-+ (BOOL)requestIsCacheEquivalent:(NSURLRequest*)a toRequest:(NSURLRequest*)b {
-    return [super requestIsCacheEquivalent:a toRequest:b];
+- (void)stopLoading {
+    [[WMFImageController sharedInstance] cancelFetchForURL:self.request.URL];
 }
 
 - (void)startLoading {
-    MWKImage* image = [self.article existingImageWithURL:[self.request.URL 
absoluteString]];
-    if ([image isCached]) {
-        [self respondWithImageData:[image asNSData]];
-    } else {
-        [self sendImageRequest:self.request];
-    }
+    DDLogVerbose(@"Fetching image %@", self.request.URL);
+    [[WMFImageController sharedInstance] fetchImageWithURL:self.request.URL]
+    .thenInBackground([self respondWithDataFromDownload])
+    .then([self sendImageDownloadedNotification])
+    .catch([self respondWithError]);
 }
 
-- (void)respondWithImageData:(NSData*)imageData {
-    NSURLResponse* response =
-        [[NSURLResponse alloc] initWithURL:self.request.URL
-                                  MIMEType:[self.request.URL.pathExtension 
wmf_mimeTypeForExtension]
-                     expectedContentLength:imageData.length
-                          textEncodingName:nil];
+#pragma mark - Callbacks
 
-    [[self client] URLProtocol:self didReceiveResponse:response 
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
-    [[self client] URLProtocol:self didLoadData:imageData];
-    [[self client] URLProtocolDidFinishLoading:self];
+- (WMFImageDownloadBlock)respondWithDataFromDownload {
+    return ^(WMFImageDownload* download) {
+               NSData* data = download.data;
+               if (!data) {
+                   // HAX: manually read the data in case the image was 
returned directly from memory
+                   data = [[WMFImageController sharedInstance] 
dataForImageWithURL:self.request.URL];
+               }
+               if (!data) {
+                   DDLogWarn(@"Disk cache miss for request: %@", self.request);
+                   data = [NSData data];
+               }
+               DDLogVerbose(@"Sending image response for %@", 
self.request.URL);
+               NSURLResponse* response =
+                   [[NSURLResponse alloc] initWithURL:self.request.URL
+                                             
MIMEType:[self.request.URL.pathExtension wmf_mimeTypeForExtension]
+                                expectedContentLength:data.length
+                                     textEncodingName:nil];
+
+               // prevent browser from caching images (hopefully?)
+               [[self client] URLProtocol:self
+                       didReceiveResponse:response
+                       cacheStoragePolicy:NSURLCacheStorageNotAllowed];
+
+               [[self client] URLProtocol:self didLoadData:data];
+               [[self client] URLProtocolDidFinishLoading:self];
+    };
 }
 
-- (void)sendImageRequest:(NSURLRequest*)request {
-    NSMutableURLRequest* mutableRequest = [request mutableCopy];
-    [NSURLProtocol setProperty:@YES 
forKey:WMFArticleImageProtocolAlreadyHandled inRequest:mutableRequest];
-    self.connection = [NSURLConnection connectionWithRequest:mutableRequest 
delegate:self];
+- (WMFImageDownloadBlock)sendImageDownloadedNotification {
+    return ^(WMFImageDownload* download) {
+               MWKImage* image = [[WMFArticleImageProtocol article] 
existingImageWithURL:self.request.URL.absoluteString];
+               // If this is a size variant, MWKImage placeholder record won't 
exist, so we'll need to create one.
+               if (!image) {
+                   // Use kMWKArticleSectionNone (section Images.plist's 
should be just the orig image urls, not
+                   // all the variants from the src set).
+                   image = [[WMFArticleImageProtocol article] 
importImageURL:self.request.URL.absoluteString
+                                                                   
sectionId:kMWKArticleSectionNone];
+               }
+               [[NSNotificationCenter defaultCenter] 
postNotificationName:WMFArticleImageSectionImageRetrievedNotification
+                                                                   object:image
+                                                                 userInfo:nil];
+    };
 }
 
-- (void)stopLoading {
-    [self.connection cancel];
-    self.connection = nil;
-}
-
-- (void)connection:(NSURLConnection*)connection 
didReceiveResponse:(NSURLResponse*)response {
-    [self.client URLProtocol:self didReceiveResponse:response 
cacheStoragePolicy:NSURLCacheStorageNotAllowed];
-    self.response         = response;
-    self.mutableImageData = [[NSMutableData alloc] init];
-}
-
-- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
-    [self.client URLProtocol:self didLoadData:data];
-    [self.mutableImageData appendData:data];
-}
-
-- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
-    [self.client URLProtocolDidFinishLoading:self];
-
-    MWKImage* image = [self saveImageToDataCache:self.mutableImageData];
-    if (image) {
-        dispatch_async(dispatch_get_main_queue(), ^(){
-            [self broadcastInfoForImage:image];
-        });
-    }
-}
-
-- (void)connection:(NSURLConnection*)connection 
didFailWithError:(NSError*)error {
-    [self.client URLProtocol:self didFailWithError:error];
-}
-
-/**
- *  Saves image to the data store record for the current article.
- *
- *  @return Returns image on success, nil on fail.
- */
-- (MWKImage*)saveImageToDataCache:(NSData*)imageData {
-    NSAssert(self.request, @"No request found.");
-    NSAssert(self.request.URL, @"No request URL found.");
-    NSAssert(imageData, @"Attempt to save Nil image data to data store for 
URL: %@", self.request.URL.absoluteString);
-
-    if (!imageData) {
-        return nil;
-    }
-
-    // "canInitWithRequest:" already determined that this image, or a size 
variant of it, has a placeholder MWKImage record.
-    MWKImage* image = [self.article 
existingImageWithURL:self.request.URL.absoluteString];
-    // If this is a size variant, MWKImage placeholder record won't exist, so 
we'll need to create one.
-    if (!image) {
-        // Use kMWKArticleSectionNone (section Images.plist's should be just 
the orig image urls, not
-        // all the variants from the src set).
-        image = [self.article importImageURL:self.request.URL.absoluteString 
sectionId:kMWKArticleSectionNone];
-    }
-
-    @try {
-        //NSLog(@"Rerouting cached response to WMF data store for %@", 
self.request);
-        [self.article importImageData:imageData image:image];
-    }@catch (NSException* e) {
-        NSAssert(false, @"Failure to save cached image data: %@ \n %@", e, 
self.request.URL.absoluteString);
-        return nil;
-    }
-
-    return image;
-}
-
-- (void)broadcastInfoForImage:(MWKImage*)image {
-    [[NSNotificationCenter defaultCenter] 
postNotificationName:WMFArticleImageSectionImageRetrievedNotification
-                                                        object:image
-                                                      userInfo:nil];
+- (PMKRejecter)respondWithError {
+    return ^(NSError* error) {
+               DDLogWarn(@"Failed to fetch image at %@ due to %@", 
self.request.URL, error);
+               [self.client URLProtocol:self didFailWithError:error];
+    };
 }
 
 @end
diff --git a/Wikipedia/Wikipedia-Bridging-Header.h 
b/Wikipedia/Wikipedia-Bridging-Header.h
index 3e43ef0..d3db1d9 100644
--- a/Wikipedia/Wikipedia-Bridging-Header.h
+++ b/Wikipedia/Wikipedia-Bridging-Header.h
@@ -1 +1,5 @@
 #import "WikipediaAppUtils.h"
+#import <SDWebImage/SDWebImageManager.h>
+#import <SDWebImage/UIImage+MultiFormat.h>
+#import "MediaWikiKit.h"
+#import "WMFGCDHelpers.h"
diff --git a/Wikipedia/mw-utils/SwiftUtilities.swift 
b/Wikipedia/mw-utils/SwiftUtilities.swift
new file mode 100644
index 0000000..1905a89
--- /dev/null
+++ b/Wikipedia/mw-utils/SwiftUtilities.swift
@@ -0,0 +1,39 @@
+//
+//  SwiftUtilities.swift
+//  Wikipedia
+//
+//  Created by Brian Gerstle on 6/23/15.
+//  Copyright (c) 2015 Wikimedia Foundation. All rights reserved.
+//
+
+import Foundation
+
+/// MARK: WrapperObject
+
+/// Allows boxing of `Any` objects (including functions) for storage in 
`Any`-incompatible Foundation collections.
+public class WrapperObject<T: Any> {
+    var value: T
+
+    public required init(value: T) {
+        self.value = value
+    }
+}
+
+/// MARK: Addressable
+
+/// Protocol that allows you to generically get the pointer address of an 
object.
+public protocol Addressable {
+    func address() -> Int
+}
+
+func address<A>(a: A) -> Int {
+    return unsafeBitCast(a, Int.self)
+}
+
+func address<A: Addressable>(a: A) -> Int {
+    return a.address()
+}
+
+public func addressString<T>(a: T) -> String {
+    return String(format: "%p", address(a))
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/220527
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I98037e1637fd8d6534a570480edc0ab1556fb44b
Gerrit-PatchSet: 10
Gerrit-Project: apps/ios/wikipedia
Gerrit-Branch: 5.0
Gerrit-Owner: Bgerstle <bgers...@wikimedia.org>
Gerrit-Reviewer: Fjalapeno <cfl...@wikimedia.org>
Gerrit-Reviewer: Mhurd <mh...@wikimedia.org>
Gerrit-Reviewer: jenkins-bot <>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to