Skip to content

Commit 06dbf83

Browse files
authored
Normalize URLs on-the-fly when comparing (#454)
1 parent 91bc159 commit 06dbf83

File tree

91 files changed

+955
-899
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

91 files changed

+955
-899
lines changed

Sources/Adapters/GCDWebServer/GCDHTTPServer.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ public class GCDHTTPServer: HTTPServer, Loggable {
138138
let pathWithoutAnchor = url.removingQuery().removingFragment()
139139

140140
for (endpoint, handler) in handlers {
141-
if endpoint == pathWithoutAnchor {
141+
if endpoint.isEquivalentTo(pathWithoutAnchor) {
142142
let request = HTTPServerRequest(url: url, href: nil)
143143
let resource = handler.onRequest(request)
144144
completion(

Sources/Adapters/GCDWebServer/ResourceResponse.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ class ResourceResponse: ReadiumGCDWebServerFileResponse, Loggable {
7777

7878
super.init()
7979

80-
contentType = resource.link.type ?? ""
80+
contentType = resource.link.mediaType?.string ?? ""
8181

8282
// Disable HTTP caching for publication resources, because it poses a security threat for protected
8383
// publications.

Sources/Navigator/Audiobook/AudioNavigator.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -339,8 +339,8 @@ open class AudioNavigator: Navigator, Configurable, AudioSessionUser, Loggable {
339339
}
340340

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

383383
@discardableResult
384384
public func go(to locator: Locator, animated: Bool = false, completion: @escaping () -> Void = {}) -> Bool {
385-
guard let newResourceIndex = publication.readingOrder.firstIndex(withHREF: locator.href) else {
385+
let locator = publication.normalizeLocator(locator)
386+
387+
guard let newResourceIndex = publication.readingOrder.firstIndexWithHREF(locator.href) else {
386388
return false
387389
}
388390
let link = publication.readingOrder[newResourceIndex]

Sources/Navigator/Audiobook/PublicationMediaLoader.swift

+13-15
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import ReadiumShared
1212
///
1313
/// Useful for local resources or when you need to customize the way HTTP requests are sent.
1414
final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {
15-
private typealias HREF = String
16-
1715
public enum AssetError: LocalizedError {
1816
case invalidHREF(String)
1917

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

3634
/// Creates a new `AVURLAsset` to serve the given `link`.
3735
func makeAsset(for link: Link) throws -> AVURLAsset {
38-
guard
39-
let originalURL = try? link.url(relativeTo: publication.baseURL),
40-
var components = URLComponents(url: originalURL.url, resolvingAgainstBaseURL: true)
41-
else {
36+
let originalURL = link.url(relativeTo: publication.baseURL)
37+
guard var components = URLComponents(url: originalURL.url, resolvingAgainstBaseURL: true) else {
4238
throw AssetError.invalidHREF(link.href)
4339
}
4440

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

5652
// MARK: - Resource Management
5753

58-
private var resources: [HREF: Resource] = [:]
54+
private var resources: [AnyURL: Resource] = [:]
5955

60-
private func resource(forHREF href: HREF) -> Resource {
61-
if let res = resources[href] {
56+
private func resource<T: URLConvertible>(forHREF href: T) -> Resource {
57+
let href = href.anyURL
58+
if let res = resources[equivalent: href] {
6259
return res
6360
}
6461

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

7471
/// List of on-going loading requests.
75-
private var requests: [HREF: [CancellableRequest]] = [:]
72+
private var requests: [AnyURL: [CancellableRequest]] = [:]
7673

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

130128
private func fillInfo(_ infoRequest: AVAssetResourceLoadingContentInformationRequest, of request: AVAssetResourceLoadingRequest, using resource: Resource) {
131129
infoRequest.isByteRangeAccessSupported = true
132-
infoRequest.contentType = resource.link.mediaType.uti
130+
infoRequest.contentType = resource.link.mediaType?.uti
133131
if case let .success(length) = resource.length {
134132
infoRequest.contentLength = Int64(length)
135133
}
@@ -153,7 +151,7 @@ final class PublicationMediaLoader: NSObject, AVAssetResourceLoaderDelegate {
153151
}
154152
)
155153

156-
registerRequest(request, cancellable: cancellable, for: resource.link.href)
154+
registerRequest(request, cancellable: cancellable, for: resource.link.url())
157155
}
158156

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

166164
extension URL {
167-
var audioHREF: String? {
165+
var audioHREF: AnyURL? {
168166
guard let url = absoluteURL, url.scheme.rawValue.hasPrefix(schemePrefix) == true else {
169167
return nil
170168
}
@@ -173,6 +171,6 @@ extension URL {
173171
// * readium:relative/file.mp3
174172
// * readiumfile:///directory/local-file.mp3
175173
// * readiumhttp(s)://domain.com/external-file.mp3
176-
return url.string.removingPrefix(schemePrefix).removingPrefix(":")
174+
return AnyURL(string: url.string.removingPrefix(schemePrefix).removingPrefix(":"))
177175
}
178176
}

Sources/Navigator/CBZ/CBZNavigatorViewController.swift

+7-8
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
8787
self.publicationEndpoint = publicationEndpoint
8888

8989
initialIndex = {
90-
guard let initialLocation = initialLocation, let initialIndex = publication.readingOrder.firstIndex(withHREF: initialLocation.href) else {
90+
guard let initialLocation = initialLocation, let initialIndex = publication.readingOrder.firstIndexWithHREF(initialLocation.href) else {
9191
return 0
9292
}
9393
return initialIndex
@@ -180,13 +180,10 @@ open class CBZNavigatorViewController: UIViewController, VisualNavigator, Loggab
180180
}
181181

182182
private func imageViewController(at index: Int) -> ImageViewController? {
183-
guard
184-
publication.readingOrder.indices.contains(index),
185-
let url = try? publication.readingOrder[index].url(relativeTo: publicationBaseURL)
186-
else {
183+
guard publication.readingOrder.indices.contains(index) else {
187184
return nil
188185
}
189-
186+
let url = publication.readingOrder[index].url(relativeTo: publicationBaseURL)
190187
return ImageViewController(index: index, url: url.url)
191188
}
192189

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

211208
public func go(to locator: Locator, animated: Bool, completion: @escaping () -> Void) -> Bool {
212-
guard let index = publication.readingOrder.firstIndex(withHREF: locator.href) else {
209+
let locator = publication.normalizeLocator(locator)
210+
211+
guard let index = publication.readingOrder.firstIndexWithHREF(locator.href) else {
213212
return false
214213
}
215214
return goToResourceAtIndex(index, animated: animated, isJump: true, completion: completion)
216215
}
217216

218217
public func go(to link: Link, animated: Bool, completion: @escaping () -> Void) -> Bool {
219-
guard let index = publication.readingOrder.firstIndex(withHREF: link.href) else {
218+
guard let index = publication.readingOrder.firstIndexWithHREF(link.url()) else {
220219
return false
221220
}
222221
return goToResourceAtIndex(index, animated: animated, isJump: true, completion: completion)

Sources/Navigator/Decorator/DiffableDecoration.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,10 @@ enum DecorationChange {
2020
}
2121

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

26-
var changes: [String: [DecorationChange]] = [:]
26+
var changes: [AnyURL: [DecorationChange]] = [:]
2727

2828
func register(_ change: DecorationChange, at locator: Locator) {
2929
var resourceChanges: [DecorationChange] = changes[locator.href] ?? []

Sources/Navigator/EPUB/EPUBFixedSpreadView.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ final class EPUBFixedSpreadView: EPUBSpreadView {
103103
goToCompletions.complete()
104104
}
105105

106-
override func evaluateScript(_ script: String, inHREF href: String?, completion: ((Result<Any, Error>) -> Void)?) {
107-
let href = href ?? ""
106+
override func evaluateScript(_ script: String, inHREF href: AnyURL? = nil, completion: ((Result<Any, any Error>) -> Void)? = nil) {
107+
let href = href?.string ?? ""
108108
let script = "spread.eval('\(href)', `\(script.replacingOccurrences(of: "\\", with: "\\\\").replacingOccurrences(of: "`", with: "\\`"))`);"
109109
super.evaluateScript(script, completion: completion)
110110
}

Sources/Navigator/EPUB/EPUBNavigatorViewController.swift

+27-18
Original file line numberDiff line numberDiff line change
@@ -431,13 +431,13 @@ open class EPUBNavigatorViewController: UIViewController,
431431
}
432432

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

438438
for link in linkList {
439439
if let title = link.title {
440-
result[link.href] = title
440+
result[link.url()] = title
441441
}
442442
let subResult = fulfill(linkList: link.children)
443443
result.merge(subResult) { current, _ -> String in
@@ -565,7 +565,7 @@ open class EPUBNavigatorViewController: UIViewController,
565565
return nil
566566
}
567567

568-
return readingOrder.firstIndex(withHREF: spreads[currentSpreadIndex].left.href)
568+
return readingOrder.firstIndexWithHREF(spreads[currentSpreadIndex].left.url())
569569
}
570570

571571
private let reloadSpreadsCompletions = CompletionList()
@@ -615,7 +615,7 @@ open class EPUBNavigatorViewController: UIViewController,
615615
)
616616

617617
let initialIndex: Int = {
618-
if let href = locator?.href, let foundIndex = self.spreads.firstIndex(withHref: href) {
618+
if let href = locator?.href, let foundIndex = self.spreads.firstIndexWithHREF(href) {
619619
return foundIndex
620620
} else {
621621
return 0
@@ -633,10 +633,10 @@ open class EPUBNavigatorViewController: UIViewController,
633633
}
634634
}
635635

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

642642
// MARK: - Navigator
@@ -671,21 +671,21 @@ open class EPUBNavigatorViewController: UIViewController,
671671
}
672672

673673
let link = spreadView.focusedResource ?? spreadView.spread.leading
674-
let href = link.href
674+
let href = link.url()
675675
let progression = min(max(spreadView.progression(in: href), 0.0), 1.0)
676676

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

728728
public func go(to locator: Locator, animated: Bool, completion: @escaping () -> Void) -> Bool {
729+
let locator = publication.normalizeLocator(locator)
730+
729731
guard
730-
let spreadIndex = spreads.firstIndex(withHref: locator.href),
732+
let spreadIndex = spreads.firstIndexWithHREF(locator.href),
731733
on(.jump(locator))
732734
else {
733735
return false
@@ -796,7 +798,11 @@ open class EPUBNavigatorViewController: UIViewController,
796798

797799
public func apply(decorations: [Decoration], in group: String) {
798800
let source = self.decorations[group] ?? []
799-
let target = decorations.map { DiffableDecoration(decoration: $0) }
801+
let target = decorations.map { d in
802+
var d = d
803+
d.locator = publication.normalizeLocator(d.locator)
804+
return DiffableDecoration(decoration: d)
805+
}
800806

801807
self.decorations[group] = target
802808

@@ -815,7 +821,7 @@ open class EPUBNavigatorViewController: UIViewController,
815821
guard let script = changes.javascript(forGroup: group, styles: config.decorationTemplates) else {
816822
continue
817823
}
818-
loadedSpreadView(forHREF: href)?.evaluateScript(script, inHREF: href)
824+
loadedSpreadViewForHREF(href)?.evaluateScript(script, inHREF: href)
819825
}
820826
}
821827
}
@@ -922,7 +928,7 @@ extension EPUBNavigatorViewController: EPUBNavigatorViewModelDelegate {
922928
for (_, view) in paginationView.loadedViews {
923929
guard
924930
let view = view as? EPUBSpreadView,
925-
view.spread.links.first(withHREF: href) != nil
931+
view.spread.links.firstWithHREF(href) != nil
926932
else {
927933
continue
928934
}
@@ -966,10 +972,10 @@ extension EPUBNavigatorViewController: EPUBSpreadViewDelegate {
966972

967973
spreadView.evaluateScript("(function() {\n\(script)\n})();") { _ in
968974
for link in spreadView.spread.links {
969-
let href = link.href
975+
let href = link.url()
970976
for (group, decorations) in self.decorations {
971977
let decorations = decorations
972-
.filter { $0.decoration.locator.href == href }
978+
.filter { $0.decoration.locator.href.isEquivalentTo(href) }
973979
.map { DecorationChange.add($0.decoration) }
974980

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

10201026
func spreadView(_ spreadView: EPUBSpreadView, didTapOnInternalLink href: String, clickEvent: ClickEvent?) {
1021-
guard var link = publication.link(withHREF: href) else {
1027+
guard
1028+
let url = AnyURL(string: href),
1029+
var link = publication.linkWithHREF(url)
1030+
else {
10221031
log(.warning, "Cannot find link with HREF: \(href)")
10231032
return
10241033
}

Sources/Navigator/EPUB/EPUBNavigatorViewModel.swift

+4-4
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ protocol EPUBNavigatorViewModelDelegate: AnyObject {
1717
enum EPUBScriptScope {
1818
case currentResource
1919
case loadedResources
20-
case resource(href: String)
20+
case resource(href: AnyURL)
2121
}
2222

2323
final class EPUBNavigatorViewModel: Loggable {
@@ -173,8 +173,8 @@ final class EPUBNavigatorViewModel: Loggable {
173173
}
174174
}
175175

176-
func url(to link: Link) -> AnyURL? {
177-
try? link.url(relativeTo: publicationBaseURL)
176+
func url(to link: Link) -> AnyURL {
177+
link.url(relativeTo: publicationBaseURL)
178178
}
179179

180180
private func serveFile(at file: FileURL, baseEndpoint: HTTPServerEndpoint) throws -> HTTPURL {
@@ -351,7 +351,7 @@ final class EPUBNavigatorViewModel: Loggable {
351351
func injectReadiumCSS(in resource: Resource) -> Resource {
352352
let link = resource.link
353353
guard
354-
link.mediaType.isHTML,
354+
link.mediaType?.isHTML == true,
355355
publication.metadata.presentation.layout(of: link) == .reflowable
356356
else {
357357
return resource

Sources/Navigator/EPUB/EPUBReflowableSpreadView.swift

+7-7
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,7 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
7777
return
7878
}
7979
let link = spread.leading
80-
guard let url = viewModel.url(to: link) else {
81-
log(.error, "Can't get URL for link \(link.href)")
82-
return
83-
}
80+
let url = viewModel.url(to: link)
8481
webView.load(URLRequest(url: url.url))
8582
}
8683

@@ -172,8 +169,11 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
172169

173170
// MARK: - Location and progression
174171

175-
override func progression(in href: String) -> Double {
176-
guard spread.leading.href == href, let progression = progression else {
172+
override func progression<T>(in href: T) -> Double where T: URLConvertible {
173+
guard
174+
spread.leading.url().isEquivalentTo(href),
175+
let progression = progression
176+
else {
177177
return 0
178178
}
179179
return progression
@@ -261,7 +261,7 @@ final class EPUBReflowableSpreadView: EPUBSpreadView {
261261
}
262262

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

0 commit comments

Comments
 (0)