Skip to content

Commit

Permalink
Normalize URLs on-the-fly when comparing (#454)
Browse files Browse the repository at this point in the history
  • Loading branch information
mickael-menu authored Jun 17, 2024
1 parent 91bc159 commit 06dbf83
Show file tree
Hide file tree
Showing 91 changed files with 955 additions and 899 deletions.
2 changes: 1 addition & 1 deletion Sources/Adapters/GCDWebServer/GCDHTTPServer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public class GCDHTTPServer: HTTPServer, Loggable {
let pathWithoutAnchor = url.removingQuery().removingFragment()

for (endpoint, handler) in handlers {
if endpoint == pathWithoutAnchor {
if endpoint.isEquivalentTo(pathWithoutAnchor) {
let request = HTTPServerRequest(url: url, href: nil)
let resource = handler.onRequest(request)
completion(
Expand Down
2 changes: 1 addition & 1 deletion Sources/Adapters/GCDWebServer/ResourceResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class ResourceResponse: ReadiumGCDWebServerFileResponse, Loggable {

super.init()

contentType = resource.link.type ?? ""
contentType = resource.link.mediaType?.string ?? ""

// Disable HTTP caching for publication resources, because it poses a security threat for protected
// publications.
Expand Down
8 changes: 5 additions & 3 deletions Sources/Navigator/Audiobook/AudioNavigator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,8 +339,8 @@ open class AudioNavigator: Navigator, Configurable, AudioSessionUser, Loggable {
}

return Locator(
href: link.href,
type: link.type ?? "audio/*",
href: link.url(),
mediaType: link.mediaType ?? MediaType("audio/*")!,
title: link.title,
locations: Locator.Locations(
fragments: ["t=\(time)"],
Expand Down Expand Up @@ -382,7 +382,9 @@ open class AudioNavigator: Navigator, Configurable, AudioSessionUser, Loggable {

@discardableResult
public func go(to locator: Locator, animated: Bool = false, completion: @escaping () -> Void = {}) -> Bool {
guard let newResourceIndex = publication.readingOrder.firstIndex(withHREF: locator.href) else {
let locator = publication.normalizeLocator(locator)

guard let newResourceIndex = publication.readingOrder.firstIndexWithHREF(locator.href) else {
return false
}
let link = publication.readingOrder[newResourceIndex]
Expand Down
28 changes: 13 additions & 15 deletions Sources/Navigator/Audiobook/PublicationMediaLoader.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import ReadiumShared
///
/// Useful for local resources or when you need to customize the way HTTP requests are sent.
final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {
private typealias HREF = String

public enum AssetError: LocalizedError {
case invalidHREF(String)

Expand All @@ -35,10 +33,8 @@ final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {

/// Creates a new `AVURLAsset` to serve the given `link`.
func makeAsset(for link: Link) throws -> AVURLAsset {
guard
let originalURL = try? link.url(relativeTo: publication.baseURL),
var components = URLComponents(url: originalURL.url, resolvingAgainstBaseURL: true)
else {
let originalURL = link.url(relativeTo: publication.baseURL)
guard var components = URLComponents(url: originalURL.url, resolvingAgainstBaseURL: true) else {
throw AssetError.invalidHREF(link.href)
}

Expand All @@ -55,10 +51,11 @@ final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {

// MARK: - Resource Management

private var resources: [HREF: Resource] = [:]
private var resources: [AnyURL: Resource] = [:]

private func resource(forHREF href: HREF) -> Resource {
if let res = resources[href] {
private func resource<T: URLConvertible>(forHREF href: T) -> Resource {
let href = href.anyURL
if let res = resources[equivalent: href] {
return res
}

Expand All @@ -72,10 +69,11 @@ final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {
private typealias CancellableRequest = (request: AVAssetResourceLoadingRequest, cancellable: Cancellable)

/// List of on-going loading requests.
private var requests: [HREF: [CancellableRequest]] = [:]
private var requests: [AnyURL: [CancellableRequest]] = [:]

/// Adds a new loading request.
private func registerRequest(_ request: AVAssetResourceLoadingRequest, cancellable: Cancellable, for href: HREF) {
private func registerRequest<T: URLConvertible>(_ request: AVAssetResourceLoadingRequest, cancellable: Cancellable, for href: T) {
let href = href.anyURL
var reqs: [CancellableRequest] = requests[href] ?? []
reqs.append((request, cancellable))
requests[href] = reqs
Expand Down Expand Up @@ -129,7 +127,7 @@ final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {

private func fillInfo(_ infoRequest: AVAssetResourceLoadingContentInformationRequest, of request: AVAssetResourceLoadingRequest, using resource: Resource) {
infoRequest.isByteRangeAccessSupported = true
infoRequest.contentType = resource.link.mediaType.uti
infoRequest.contentType = resource.link.mediaType?.uti
if case let .success(length) = resource.length {
infoRequest.contentLength = Int64(length)
}
Expand All @@ -153,7 +151,7 @@ final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {
}
)

registerRequest(request, cancellable: cancellable, for: resource.link.href)
registerRequest(request, cancellable: cancellable, for: resource.link.url())
}

func resourceLoader(_ resourceLoader: AVAssetResourceLoader, didCancel loadingRequest: AVAssetResourceLoadingRequest) {
Expand All @@ -164,7 +162,7 @@ final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {
private let schemePrefix = "readium"

extension URL {
var audioHREF: String? {
var audioHREF: AnyURL? {
guard let url = absoluteURL, url.scheme.rawValue.hasPrefix(schemePrefix) == true else {
return nil
}
Expand All @@ -173,6 +171,6 @@ extension URL {
// * readium:relative/file.mp3
// * readiumfile:///directory/local-file.mp3
// * readiumhttp(s)://domain.com/external-file.mp3
return url.string.removingPrefix(schemePrefix).removingPrefix(":")
return AnyURL(string: url.string.removingPrefix(schemePrefix).removingPrefix(":"))
}
}
15 changes: 7 additions & 8 deletions Sources/Navigator/CBZ/CBZNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
self.publicationEndpoint = publicationEndpoint

initialIndex = {
guard let initialLocation = initialLocation, let initialIndex = publication.readingOrder.firstIndex(withHREF: initialLocation.href) else {
guard let initialLocation = initialLocation, let initialIndex = publication.readingOrder.firstIndexWithHREF(initialLocation.href) else {
return 0
}
return initialIndex
Expand Down Expand Up @@ -180,13 +180,10 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
}

private func imageViewController(at index: Int) -> ImageViewController? {
guard
publication.readingOrder.indices.contains(index),
let url = try? publication.readingOrder[index].url(relativeTo: publicationBaseURL)
else {
guard publication.readingOrder.indices.contains(index) else {
return nil
}

let url = publication.readingOrder[index].url(relativeTo: publicationBaseURL)
return ImageViewController(index: index, url: url.url)
}

Expand All @@ -209,14 +206,16 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
}

public func go(to locator: Locator, animated: Bool, completion: @escaping () -> Void) -> Bool {
guard let index = publication.readingOrder.firstIndex(withHREF: locator.href) else {
let locator = publication.normalizeLocator(locator)

guard let index = publication.readingOrder.firstIndexWithHREF(locator.href) else {
return false
}
return goToResourceAtIndex(index, animated: animated, isJump: true, completion: completion)
}

public func go(to link: Link, animated: Bool, completion: @escaping () -> Void) -> Bool {
guard let index = publication.readingOrder.firstIndex(withHREF: link.href) else {
guard let index = publication.readingOrder.firstIndexWithHREF(link.url()) else {
return false
}
return goToResourceAtIndex(index, animated: animated, isJump: true, completion: completion)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Navigator/Decorator/DiffableDecoration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ enum DecorationChange {
}

extension Array where Element == DiffableDecoration {
func changesByHREF(from source: [DiffableDecoration]) -> [String: [DecorationChange]] {
func changesByHREF(from source: [DiffableDecoration]) -> [AnyURL: [DecorationChange]] {
let changeset = StagedChangeset(source: source, target: self)

var changes: [String: [DecorationChange]] = [:]
var changes: [AnyURL: [DecorationChange]] = [:]

func register(_ change: DecorationChange, at locator: Locator) {
var resourceChanges: [DecorationChange] = changes[locator.href] ?? []
Expand Down
4 changes: 2 additions & 2 deletions Sources/Navigator/EPUB/EPUBFixedSpreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,8 @@ final class EPUBFixedSpreadView: EPUBSpreadView {
goToCompletions.complete()
}

override func evaluateScript(_ script: String, inHREF href: String?, completion: ((Result<Any, Error>) -> Void)?) {
let href = href ?? ""
override func evaluateScript(_ script: String, inHREF href: AnyURL? = nil, completion: ((Result<Any, any Error>) -> Void)? = nil) {
let href = href?.string ?? ""
let script = "spread.eval('\(href)', `\(script.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "`", with: "\\`"))`);"
super.evaluateScript(script, completion: completion)
}
Expand Down
45 changes: 27 additions & 18 deletions Sources/Navigator/EPUB/EPUBNavigatorViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -431,13 +431,13 @@ open class EPUBNavigatorViewController: UIViewController,
}

/// Mapping between reading order hrefs and the table of contents title.
private lazy var tableOfContentsTitleByHref: [String: String] = {
func fulfill(linkList: [Link]) -> [String: String] {
var result = [String: String]()
private lazy var tableOfContentsTitleByHref: [AnyURL: String] = {
func fulfill(linkList: [Link]) -> [AnyURL: String] {
var result = [AnyURL: String]()

for link in linkList {
if let title = link.title {
result[link.href] = title
result[link.url()] = title
}
let subResult = fulfill(linkList: link.children)
result.merge(subResult) { current, _ -> String in
Expand Down Expand Up @@ -565,7 +565,7 @@ open class EPUBNavigatorViewController: UIViewController,
return nil
}

return readingOrder.firstIndex(withHREF: spreads[currentSpreadIndex].left.href)
return readingOrder.firstIndexWithHREF(spreads[currentSpreadIndex].left.url())
}

private let reloadSpreadsCompletions = CompletionList()
Expand Down Expand Up @@ -615,7 +615,7 @@ open class EPUBNavigatorViewController: UIViewController,
)

let initialIndex: Int = {
if let href = locator?.href, let foundIndex = self.spreads.firstIndex(withHref: href) {
if let href = locator?.href, let foundIndex = self.spreads.firstIndexWithHREF(href) {
return foundIndex
} else {
return 0
Expand All @@ -633,10 +633,10 @@ open class EPUBNavigatorViewController: UIViewController,
}
}

private func loadedSpreadView(forHREF href: String) -> EPUBSpreadView? {
private func loadedSpreadViewForHREF<T: URLConvertible>(_ href: T) -> EPUBSpreadView? {
paginationView.loadedViews
.compactMap { _, view in view as? EPUBSpreadView }
.first { $0.spread.links.first(withHREF: href) != nil }
.first { $0.spread.links.firstWithHREF(href) != nil }
}

// MARK: - Navigator
Expand Down Expand Up @@ -671,21 +671,21 @@ open class EPUBNavigatorViewController: UIViewController,
}

let link = spreadView.focusedResource ?? spreadView.spread.leading
let href = link.href
let href = link.url()
let progression = min(max(spreadView.progression(in: href), 0.0), 1.0)

if
// The positions are not always available, for example a Readium
// WebPub doesn't have any unless a Publication Positions Web
// Service is provided
let index = readingOrder.firstIndex(withHREF: href),
let index = readingOrder.firstIndexWithHREF(href),
let positionList = positionsByReadingOrder.getOrNil(index),
positionList.count > 0
{
// Gets the current locator from the positionList, and fill its missing data.
let positionIndex = Int(ceil(progression * Double(positionList.count - 1)))
return positionList[positionIndex].copy(
title: tableOfContentsTitleByHref[href],
title: tableOfContentsTitleByHref[equivalent: href],
locations: { $0.progression = progression }
)
} else {
Expand Down Expand Up @@ -726,8 +726,10 @@ open class EPUBNavigatorViewController: UIViewController,
}

public func go(to locator: Locator, animated: Bool, completion: @escaping () -> Void) -> Bool {
let locator = publication.normalizeLocator(locator)

guard
let spreadIndex = spreads.firstIndex(withHref: locator.href),
let spreadIndex = spreads.firstIndexWithHREF(locator.href),
on(.jump(locator))
else {
return false
Expand Down Expand Up @@ -796,7 +798,11 @@ open class EPUBNavigatorViewController: UIViewController,

public func apply(decorations: [Decoration], in group: String) {
let source = self.decorations[group] ?? []
let target = decorations.map { DiffableDecoration(decoration: $0) }
let target = decorations.map { d in
var d = d
d.locator = publication.normalizeLocator(d.locator)
return DiffableDecoration(decoration: d)
}

self.decorations[group] = target

Expand All @@ -815,7 +821,7 @@ open class EPUBNavigatorViewController: UIViewController,
guard let script = changes.javascript(forGroup: group, styles: config.decorationTemplates) else {
continue
}
loadedSpreadView(forHREF: href)?.evaluateScript(script, inHREF: href)
loadedSpreadViewForHREF(href)?.evaluateScript(script, inHREF: href)
}
}
}
Expand Down Expand Up @@ -922,7 +928,7 @@ extension EPUBNavigatorViewController: EPUBNavigatorViewModelDelegate {
for (_, view) in paginationView.loadedViews {
guard
let view = view as? EPUBSpreadView,
view.spread.links.first(withHREF: href) != nil
view.spread.links.firstWithHREF(href) != nil
else {
continue
}
Expand Down Expand Up @@ -966,10 +972,10 @@ extension EPUBNavigatorViewController: EPUBSpreadViewDelegate {

spreadView.evaluateScript("(function() {\n\(script)\n})();") { _ in
for link in spreadView.spread.links {
let href = link.href
let href = link.url()
for (group, decorations) in self.decorations {
let decorations = decorations
.filter { $0.decoration.locator.href == href }
.filter { $0.decoration.locator.href.isEquivalentTo(href) }
.map { DecorationChange.add($0.decoration) }

guard let script = decorations.javascript(forGroup: group, styles: self.config.decorationTemplates) else {
Expand Down Expand Up @@ -1018,7 +1024,10 @@ extension EPUBNavigatorViewController: EPUBSpreadViewDelegate {
}

func spreadView(_ spreadView: EPUBSpreadView, didTapOnInternalLink href: String, clickEvent: ClickEvent?) {
guard var link = publication.link(withHREF: href) else {
guard
let url = AnyURL(string: href),
var link = publication.linkWithHREF(url)
else {
log(.warning, "Cannot find link with HREF: \(href)")
return
}
Expand Down
8 changes: 4 additions & 4 deletions Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ protocol EPUBNavigatorViewModelDelegate: AnyObject {
enum EPUBScriptScope {
case currentResource
case loadedResources
case resource(href: String)
case resource(href: AnyURL)
}

final class EPUBNavigatorViewModel: Loggable {
Expand Down Expand Up @@ -173,8 +173,8 @@ final class EPUBNavigatorViewModel: Loggable {
}
}

func url(to link: Link) -> AnyURL? {
try? link.url(relativeTo: publicationBaseURL)
func url(to link: Link) -> AnyURL {
link.url(relativeTo: publicationBaseURL)
}

private func serveFile(at file: FileURL, baseEndpoint: HTTPServerEndpoint) throws -> HTTPURL {
Expand Down Expand Up @@ -351,7 +351,7 @@ final class EPUBNavigatorViewModel: Loggable {
func injectReadiumCSS(in resource: Resource) -> Resource {
let link = resource.link
guard
link.mediaType.isHTML,
link.mediaType?.isHTML == true,
publication.metadata.presentation.layout(of: link) == .reflowable
else {
return resource
Expand Down
14 changes: 7 additions & 7 deletions Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,7 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
return
}
let link = spread.leading
guard let url = viewModel.url(to: link) else {
log(.error, "Can't get URL for link \(link.href)")
return
}
let url = viewModel.url(to: link)
webView.load(URLRequest(url: url.url))
}

Expand Down Expand Up @@ -172,8 +169,11 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {

// MARK: - Location and progression

override func progression(in href: String) -> Double {
guard spread.leading.href == href, let progression = progression else {
override func progression<T>(in href: T) -> Double where T: URLConvertible {
guard
spread.leading.url().isEquivalentTo(href),
let progression = progression
else {
return 0
}
return progression
Expand Down Expand Up @@ -261,7 +261,7 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
}

private func go(to locator: Locator, completion: @escaping (Bool) -> Void) {
guard ["", "#"].contains(locator.href) || spread.contains(href: locator.href) else {
guard ["", "#"].contains(locator.href.string) || spread.contains(href: locator.href) else {
log(.warning, "The locator's href is not in the spread")
completion(false)
return
Expand Down
Loading

0 comments on commit 06dbf83

Please sign in to comment.