Skip to content

Commit 99d15e3

Browse files
committed
Deprecate Locator(link: Link) in favor of Publication.locate(Link) (#47)
1 parent 9d77501 commit 99d15e3

31 files changed

+348
-198
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Package.resolved
88

99
# Carthage
10-
Carthage/
10+
./Carthage/
1111
Cartfile.resolved
1212

1313
# Xcode

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ All notable changes to this project will be documented in this file. Take a look
66

77
## [Unreleased]
88

9+
### Deprecated
10+
11+
#### Shared
12+
13+
* `Locator(link: Link)` is deprecated as it may create an incorrect `Locator` if the link `type` is missing.
14+
* Use `publication.locate(Link)` instead.
15+
916
### Fixed
1017

1118
#### Navigator

Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,9 @@ scripts:
1616
yarn --cwd "$(SCRIPTS_PATH)" run format
1717
yarn --cwd "$(SCRIPTS_PATH)" run lint
1818
yarn --cwd "$(SCRIPTS_PATH)" run bundle
19+
20+
.PHONY: test
21+
test:
22+
# To limit to a particular test suite: -only-testing:R2SharedTests
23+
xcodebuild test -scheme "Readium-Package" -destination "platform=iOS Simulator,name=iPhone 12" | xcbeautify -q
24+

Sources/Navigator/Audiobook/AudioNavigator.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ open class _AudioNavigator: _MediaNavigator, _AudioSessionUser, Loggable {
2727
public init(publication: Publication, initialLocation: Locator? = nil) {
2828
self.publication = publication
2929
self.initialLocation = initialLocation
30-
?? publication.readingOrder.first.map { Locator(link: $0) }
30+
?? publication.readingOrder.first.flatMap { publication.locate($0) }
3131

3232
let durations = publication.readingOrder.map { $0.duration ?? 0 }
3333
self.durations = durations
@@ -255,7 +255,10 @@ open class _AudioNavigator: _MediaNavigator, _AudioSessionUser, Loggable {
255255

256256
@discardableResult
257257
public func go(to link: Link, animated: Bool = false, completion: @escaping () -> Void = {}) -> Bool {
258-
return go(to: Locator(link: link), animated: animated, completion: completion)
258+
guard let locator = publication.locate(link) else {
259+
return false
260+
}
261+
return go(to: locator, animated: animated, completion: completion)
259262
}
260263

261264
@discardableResult

Sources/Navigator/CBZ/CBZNavigatorViewController.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ extension CBZNavigatorViewController {
232232
public convenience init(for publication: Publication, initialIndex: Int = 0) {
233233
var location: Locator? = nil
234234
if publication.readingOrder.indices.contains(initialIndex) {
235-
location = Locator(link: publication.readingOrder[initialIndex])
235+
location = publication.locate(publication.readingOrder[initialIndex])
236236
}
237237
self.init(publication: publication, initialLocation: location)
238238
}

Sources/Navigator/EPUB/EPUBNavigatorViewController.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,7 +520,7 @@ open class EPUBNavigatorViewController: UIViewController, VisualNavigator, Selec
520520
locations: { $0.progression = progression }
521521
)
522522
} else {
523-
return Locator(link: link).copy(
523+
return publication.locate(link)?.copy(
524524
locations: { $0.progression = progression }
525525
)
526526
}
@@ -564,7 +564,10 @@ open class EPUBNavigatorViewController: UIViewController, VisualNavigator, Selec
564564
}
565565

566566
public func go(to link: Link, animated: Bool, completion: @escaping () -> Void) -> Bool {
567-
return go(to: Locator(link: link), animated: animated, completion: completion)
567+
guard let locator = publication.locate(link) else {
568+
return false
569+
}
570+
return go(to: locator, animated: animated, completion: completion)
568571
}
569572

570573
public func goForward(animated: Bool, completion: @escaping () -> Void) -> Bool {

Sources/Shared/Publication/Locator.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ public struct Locator: Hashable, CustomStringConvertible, Loggable {
7070

7171
try self.init(json: json, warnings: warnings)
7272
}
73-
73+
74+
@available(*, deprecated, message: "This may create an incorrect `Locator` if the link `type` is missing. Use `publication.locate(Link)` instead.")
7475
public init(link: Link) {
7576
let components = link.href.split(separator: "#", maxSplits: 1).map(String.init)
7677
let fragments = (components.count > 1) ? [String(components[1])] : []

Sources/Shared/Publication/Manifest.swift

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,36 @@ public struct Manifest: JSONEquatable, Hashable {
126126

127127
return metadata.conformsTo.contains(profile)
128128
}
129-
129+
130+
/// Finds the first Link having the given `href` in the manifest's links.
131+
public func link(withHREF href: String) -> Link? {
132+
func deepFind(in linkLists: [Link]...) -> Link? {
133+
for links in linkLists {
134+
for link in links {
135+
if link.href == href {
136+
return link
137+
} else if let child = deepFind(in: link.alternates, link.children) {
138+
return child
139+
}
140+
}
141+
}
142+
143+
return nil
144+
}
145+
146+
var link = deepFind(in: readingOrder, resources, links)
147+
if
148+
link == nil,
149+
let shortHREF = href.components(separatedBy: .init(charactersIn: "#?")).first,
150+
shortHREF != href
151+
{
152+
// Tries again, but without the anchor and query parameters.
153+
link = self.link(withHREF: shortHREF)
154+
}
155+
156+
return link
157+
}
158+
130159
/// Finds the first link with the given relation in the manifest's links.
131160
public func link(withRel rel: LinkRelation) -> Link? {
132161
return readingOrder.first(withRel: rel)

Sources/Shared/Publication/Publication.swift

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public class Publication: Loggable {
8484

8585
/// Returns whether this publication conforms to the given Readium Web Publication Profile.
8686
public func conforms(to profile: Profile) -> Bool {
87-
return manifest.conforms(to: profile)
87+
manifest.conforms(to: profile)
8888
}
8989

9090
/// The URL where this publication is served, computed from the `Link` with `self` relation.
@@ -97,41 +97,17 @@ public class Publication: Loggable {
9797

9898
/// Finds the first Link having the given `href` in the publication's links.
9999
public func link(withHREF href: String) -> Link? {
100-
func deepFind(in linkLists: [Link]...) -> Link? {
101-
for links in linkLists {
102-
for link in links {
103-
if link.href == href {
104-
return link
105-
} else if let child = deepFind(in: link.alternates, link.children) {
106-
return child
107-
}
108-
}
109-
}
110-
111-
return nil
112-
}
113-
114-
var link = deepFind(in: readingOrder, resources, links)
115-
if
116-
link == nil,
117-
let shortHREF = href.components(separatedBy: .init(charactersIn: "#?")).first,
118-
shortHREF != href
119-
{
120-
// Tries again, but without the anchor and query parameters.
121-
link = self.link(withHREF: shortHREF)
122-
}
123-
124-
return link
100+
manifest.link(withHREF: href)
125101
}
126102

127103
/// Finds the first link with the given relation in the publication's links.
128104
public func link(withRel rel: LinkRelation) -> Link? {
129-
return manifest.link(withRel: rel)
105+
manifest.link(withRel: rel)
130106
}
131107

132108
/// Finds all the links with the given relation in the publication's links.
133109
public func links(withRel rel: LinkRelation) -> [Link] {
134-
return manifest.links(withRel: rel)
110+
manifest.links(withRel: rel)
135111
}
136112

137113
/// Returns the resource targeted by the given `link`.
@@ -161,12 +137,12 @@ public class Publication: Loggable {
161137
///
162138
/// e.g. `findService(PositionsService.self)`
163139
public func findService<T>(_ serviceType: T.Type) -> T? {
164-
return services.first { $0 is T } as? T
140+
services.first { $0 is T } as? T
165141
}
166142

167143
/// Finds all the services implementing the given service type.
168144
public func findServices<T>(_ serviceType: T.Type) -> [T] {
169-
return services.filter { $0 is T } as! [T]
145+
services.filter { $0 is T } as! [T]
170146
}
171147

172148
/// Sets the URL where this `Publication`'s RWPM manifest is served.

Sources/Shared/Publication/Services/Locator/DefaultLocatorService.swift

Lines changed: 51 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,35 +8,70 @@ import Foundation
88

99
/// A default implementation of the `LocatorService` using the `PositionsService` to locate its inputs.
1010
open class DefaultLocatorService: LocatorService, Loggable {
11-
12-
let readingOrder: [Link]
13-
let positionsByReadingOrder: () -> [[Locator]]
14-
15-
public init(readingOrder: [Link], positionsByReadingOrder: @escaping () -> [[Locator]]) {
16-
self.readingOrder = readingOrder
17-
self.positionsByReadingOrder = positionsByReadingOrder
18-
}
1911

20-
public convenience init(readingOrder: [Link], publication: Weak<Publication>) {
21-
self.init(readingOrder: readingOrder, positionsByReadingOrder: { publication()?.positionsByReadingOrder ?? [] })
12+
public let publication: Weak<Publication>
13+
14+
public init(publication: Weak<Publication>) {
15+
self.publication = publication
2216
}
2317

18+
/// Locates the target of the given `locator`.
19+
///
20+
/// If `locator.href` can be found in the links, `locator` will be returned directly.
21+
/// Otherwise, will attempt to find the closest match using `totalProgression`, `position`,
22+
/// `fragments`, etc.
2423
open func locate(_ locator: Locator) -> Locator? {
25-
guard readingOrder.firstIndex(withHREF: locator.href) != nil else {
24+
guard let publication = publication() else {
2625
return nil
2726
}
28-
29-
return locator
27+
28+
if publication.link(withHREF: locator.href) != nil {
29+
return locator
30+
}
31+
32+
if let totalProgression = locator.locations.totalProgression, let target = locate(progression: totalProgression) {
33+
return target.copy(
34+
title: locator.title,
35+
text: { $0 = locator.text }
36+
)
37+
}
38+
39+
return nil
3040
}
31-
41+
42+
open func locate(_ link: Link) -> Locator? {
43+
let components = link.href.split(separator: "#", maxSplits: 1).map(String.init)
44+
let href = components.first ?? link.href
45+
let fragment = components.getOrNil(1)
46+
47+
guard
48+
let resourceLink = publication()?.link(withHREF: href),
49+
let type = resourceLink.type
50+
else {
51+
return nil
52+
}
53+
54+
return Locator(
55+
href: href,
56+
type: type,
57+
title: resourceLink.title ?? link.title,
58+
locations: Locator.Locations(
59+
fragments: Array(ofNotNil: fragment),
60+
progression: (fragment == nil) ? 0.0 : nil
61+
)
62+
)
63+
}
64+
3265
open func locate(progression totalProgression: Double) -> Locator? {
3366
guard 0.0...1.0 ~= totalProgression else {
3467
log(.error, "Progression must be between 0.0 and 1.0, received \(totalProgression)")
3568
return nil
3669
}
3770

38-
let positions = positionsByReadingOrder()
39-
guard let (readingOrderIndex, position) = findClosest(to: totalProgression, in: positions) else {
71+
guard
72+
let positions = publication()?.positionsByReadingOrder,
73+
let (readingOrderIndex, position) = findClosest(to: totalProgression, in: positions)
74+
else {
4075
return nil
4176
}
4277

0 commit comments

Comments
 (0)