From 850218cf4e3241afbf3d191f06734c0ae34e5074 Mon Sep 17 00:00:00 2001 From: Leon Breedt Date: Tue, 27 Oct 2015 19:03:43 +1300 Subject: [PATCH] Clean up potential race condition with downloading and parsing extra files for icons. Fixes #3. --- .../UserInterfaceState.xcuserstate | Bin 54577 -> 54521 bytes FavIcon/FavIcon.swift | 97 +++++++++--------- 2 files changed, 50 insertions(+), 47 deletions(-) diff --git a/FavIcon.xcodeproj/project.xcworkspace/xcuserdata/ljb.xcuserdatad/UserInterfaceState.xcuserstate b/FavIcon.xcodeproj/project.xcworkspace/xcuserdata/ljb.xcuserdatad/UserInterfaceState.xcuserstate index 3bac7a28a93e982725d93d3f5915f846c53a5391..1c37de75e53d0ce4699fd113ed0bb18fb99426c9 100644 GIT binary patch delta 3561 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 delta 3582 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 elements. + /// Scans a base URL, attempting to determine all of the supported icons that can + /// be used for favicon purposes. + /// + /// It will do the following to determine possible icons that can be used: + /// - Check whether or not `/favicon.ico` exists. + /// - If the base URL returns an HTML page, parse the `` section and check for `` + /// and `` tags that reference icons using Apple, Microsoft and Google + /// conventions. + /// - If _Web Application Manifest JSON_ (`manifest.json`) files are referenced, or + /// _Microsoft browser configuration XML_ (`browserconfig.xml`) files + /// are referenced, download and parse them to check if they reference icons. + /// + /// All of this work is performed in a background queue. /// /// - Parameters: - /// - url: The URL to interrogate for the presence of icons. - /// - completion: The callback to invoke when detection has completed. The caller - /// must not make any assumptions about which dispatch queue the completion - /// will be invoked on. - /// - Returns: The list of `DetectedIcon`s representing the icons that were found. + /// - url: The base URL to scan. + /// - completion: A callback to invoke when the scan has completed. The callback will be invoked + /// from a background queue. + /// - Returns: An array of `DetectedIcon`s for the icons that were found. public static func detect(url: NSURL, completion: [DetectedIcon] -> Void) { - let operations = [ + let detectionOperations = [ DownloadTextOperation(url: url), CheckURLExistsOperation(url: NSURL(string: "/favicon.ico", relativeToURL: url)!.absoluteURL), CheckURLExistsOperation(url: NSURL(string: "/browserconfig.xml", relativeToURL: url)!.absoluteURL) ] - executeURLOperations(operations) { results in + executeURLOperations(detectionOperations) { detectionResults in var icons: [DetectedIcon] = [] + var additionalDownloads: [(URLRequestOperation, URLResult -> Void)] = [] - var manifestIcons: [DetectedIcon] = [] - var browserConfigIcons: [DetectedIcon] = [] - var additionalFileQueue: NSOperationQueue? = nil - - switch results[0] { + switch detectionResults[0] { case .TextDownloaded(let actualURL, let text, let contentType): if contentType == "text/html" { let document = HTMLDocument(string: text) - // Check for Web App manifest, trigger download and processing if required. + // 1. Extract any icons referenced by or other elements from the HTML. + icons.appendContentsOf(extractHTMLHeadIcons(document, baseURL: actualURL)) + + // Check for Web App Manifest, if present, additional download and processing to do. for link in document.query("/html/head/link") { if let rel = link.attributes["rel"]?.lowercaseString where rel == "manifest", let href = link.attributes["href"], let manifestURL = NSURL(string: href, relativeToURL: url) { - additionalFileQueue = NSOperationQueue() - - executeURLOperations([DownloadTextOperation(url: manifestURL.absoluteURL)], queue: additionalFileQueue) { manifestResults in - switch manifestResults[0] { + additionalDownloads.append((DownloadTextOperation(url: manifestURL), { manifestResult in + switch manifestResult { case .TextDownloaded( _, let manifestJSON, _): - manifestIcons = extractManifestJSONIcons(manifestJSON, baseURL: actualURL) + icons.appendContentsOf(extractManifestJSONIcons(manifestJSON, baseURL: actualURL)) break default: break } - } + })) } } - // Check for Microsoft browser configuration XML, trigger download and processing if present and not disabled. - var browserConfigURL: NSURL? = operations[2].urlRequest.URL - switch results[2] { + // Check for Microsoft browser configuration XML, if present, additional download and processing to do. + var browserConfigURL: NSURL? = detectionOperations[2].urlRequest.URL + switch detectionResults[2] { case .Success(let actualURL): browserConfigURL = actualURL break @@ -152,46 +157,44 @@ public class FavIcons { } } } - if let browserConfigURL = browserConfigURL { - if additionalFileQueue == nil { - additionalFileQueue = NSOperationQueue() - } - - executeURLOperations([DownloadTextOperation(url: browserConfigURL)], queue: additionalFileQueue) { browserConfigResults in - switch browserConfigResults[0] { + additionalDownloads.append((DownloadTextOperation(url: browserConfigURL), { browserConfigResult in + switch browserConfigResult { case .TextDownloaded( _, let browserConfigXML, _): let document = XMLDocument(string: browserConfigXML) - browserConfigIcons = extractBrowserConfigXMLIcons(document, baseURL: actualURL) + icons.appendContentsOf(extractBrowserConfigXMLIcons(document, baseURL: actualURL)) break default: break } - } + })) } - - // Extract any icons referenced by or other elements from the HTML. - icons.appendContentsOf(extractHTMLHeadIcons(document, baseURL: actualURL)) } break default: break } - switch results[1] { + switch detectionResults[1] { case .Success(let actualURL): icons.append(DetectedIcon(url: actualURL, type: .FavIcon)) break default: break } - if additionalFileQueue != nil { - additionalFileQueue?.waitUntilAllOperationsAreFinished() - additionalFileQueue = nil - icons.appendContentsOf(manifestIcons) - icons.appendContentsOf(browserConfigIcons) + if additionalDownloads.count > 0 { + let additionalOperations = additionalDownloads.map { $0.0 } + let additionalCompletions = additionalDownloads.map { $0.1 } + + executeURLOperations(additionalOperations) { additionalResults in + for (index, result) in additionalResults.enumerate() { + additionalCompletions[index](result) + } + + completion(icons) + } + } else { + completion(icons) } - - completion(icons) } }