Skip to content

Commit 251d7ca

Browse files
authored
Deprecate Link.toLocator() in favor of Publication.locatorFromLink() (#83)
1 parent ac34d98 commit 251d7ca

File tree

17 files changed

+186
-94
lines changed

17 files changed

+186
-94
lines changed

CHANGELOG.md

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

4444
* `Publication.type` is now deprecated in favor of the new `Publication.conformsTo()` API which is more accurate.
4545
* For example, replace `publication.type == Publication.TYPE.EPUB` with `publication.conformsTo(Publication.Profile.EPUB)` before opening a publication with the `EpubNavigatorFragment`.
46+
* `Link.toLocator()` is deprecated as it may create an incorrect `Locator` if the link `type` is missing.
47+
* Use `publication.locatorFromLink()` instead.
4648

4749
### Fixed
4850

readium/navigator-media2/src/main/java/org/readium/navigator/media2/MediaNavigator.kt

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ class MediaNavigator private constructor(
8282
if (itemStartPosition == null) null
8383
else totalDuration?.let { (itemStartPosition + position) / it }
8484

85-
return link.toLocator().copyWithLocations(
85+
val locator = requireNotNull(publication.locatorFromLink(link))
86+
return locator.copyWithLocations(
8687
fragments = listOf("t=${position.inWholeSeconds}"),
8788
progression = item.duration?.let { position / it },
8889
totalProgression = totalProgression
@@ -164,7 +165,7 @@ class MediaNavigator private constructor(
164165
*/
165166
suspend fun go(locator: Locator): Try<Unit, Exception> {
166167
val itemIndex = publication.readingOrder.indexOfFirstWithHref(locator.href)
167-
?: return Try.failure(Exception.IllegalArgument("Invalid href ${locator.href}."))
168+
?: return Try.failure(Exception.InvalidArgument("Invalid href ${locator.href}."))
168169
val position = locator.locations.time ?: Duration.ZERO
169170
Timber.v("Go to locator $locator")
170171
return seek(itemIndex, position)
@@ -173,8 +174,11 @@ class MediaNavigator private constructor(
173174
/**
174175
* Seeks to the beginning of the given link.
175176
*/
176-
suspend fun go(link: Link) =
177-
go(link.toLocator())
177+
suspend fun go(link: Link): Try<Unit, Exception> {
178+
val locator = publication.locatorFromLink(link)
179+
?: return Try.failure(Exception.InvalidArgument("Resource not found at ${link.href}"))
180+
return go(locator)
181+
}
178182

179183
/**
180184
* Skips to a little amount of time later.
@@ -274,16 +278,13 @@ class MediaNavigator private constructor(
274278
Ongoing
275279
}
276280

277-
sealed class Exception : kotlin.Exception() {
281+
sealed class Exception(override val message: String) : kotlin.Exception(message) {
278282

279283
class SessionPlayer internal constructor(
280-
internal val error: SessionPlayerError,
281-
override val message: String = "${error.name} error occurred in SessionPlayer."
282-
) : Exception()
284+
internal val error: SessionPlayerError
285+
) : Exception("${error.name} error occurred in SessionPlayer.")
283286

284-
class IllegalArgument internal constructor(
285-
override val message: String
286-
): Exception()
287+
class InvalidArgument(message: String): Exception(message)
287288
}
288289

289290
/*

readium/navigator/src/main/java/org/readium/r2/navigator/audiobook/R2AudiobookActivity.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit
7878
}
7979

8080
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean {
81-
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
81+
val locator = publication.locatorFromLink(link) ?: return false
82+
return go(locator)
8283
}
8384

8485
override fun goForward(animated: Boolean, completion: () -> Unit): Boolean {
@@ -156,7 +157,7 @@ open class R2AudiobookActivity : AppCompatActivity(), CoroutineScope, IR2Activit
156157
if (this.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
157158

158159
if (!loadedInitialLocator) {
159-
go(publication.readingOrder.first().toLocator())
160+
go(publication.readingOrder.first())
160161
}
161162

162163
binding.seekBar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {

readium/navigator/src/main/java/org/readium/r2/navigator/epub/EpubNavigatorFragment.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,8 @@ class EpubNavigatorFragment private constructor(
327327
}
328328

329329
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean {
330-
return go(link.toLocator(), animated, completion)
330+
val locator = publication.locatorFromLink(link) ?: return false
331+
return go(locator, animated, completion)
331332
}
332333

333334
private fun run(commands: List<RunScriptCommand>) {
@@ -638,7 +639,9 @@ class EpubNavigatorFragment private constructor(
638639
get() = publication.metadata.effectiveReadingProgression
639640

640641
override val currentLocator: StateFlow<Locator> get() = _currentLocator
641-
private val _currentLocator = MutableStateFlow(initialLocator ?: publication.readingOrder.first().toLocator())
642+
private val _currentLocator = MutableStateFlow(initialLocator
643+
?: requireNotNull(publication.locatorFromLink(publication.readingOrder.first()))
644+
)
642645

643646
/**
644647
* While scrolling we receive a lot of new current locations, so we use a coroutine job to

readium/navigator/src/main/java/org/readium/r2/navigator/image/ImageNavigatorFragment.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ class ImageNavigatorFragment private constructor(
6060
private lateinit var currentActivity: FragmentActivity
6161

6262
override val currentLocator: StateFlow<Locator> get() = _currentLocator
63-
private val _currentLocator = MutableStateFlow(initialLocator ?: publication.readingOrder.first().toLocator())
63+
private val _currentLocator = MutableStateFlow(initialLocator
64+
?: requireNotNull(publication.locatorFromLink(publication.readingOrder.first()))
65+
)
6466

6567
internal var currentPagerPosition: Int = 0
6668
internal var resources: List<String> = emptyList()
@@ -164,8 +166,10 @@ class ImageNavigatorFragment private constructor(
164166
return true
165167
}
166168

167-
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean =
168-
go(link.toLocator(), animated, completion)
169+
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean {
170+
val locator = publication.locatorFromLink(link) ?: return false
171+
return go(locator, animated, completion)
172+
}
169173

170174
override fun goForward(animated: Boolean, completion: () -> Unit): Boolean {
171175
val current = resourcePager.currentItem

readium/navigator/src/main/java/org/readium/r2/navigator/media/MediaService.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ open class MediaService : MediaBrowserServiceCompat(), CoroutineScope by MainSco
139139
}
140140

141141
val locator = (extras?.getParcelable(EXTRA_LOCATOR) as? Locator)
142-
?: href?.let { navigator.publication.linkWithHref(it)?.toLocator() }
142+
?: href
143+
?.let { navigator.publication.linkWithHref(it) }
144+
?.let { navigator.publication.locatorFromLink(it) }
143145

144146
if (locator != null && href != null && locator.href != href) {
145147
Timber.e("Ambiguous playback location provided. HREF `$href` doesn't match locator $locator.")
@@ -310,7 +312,12 @@ open class MediaService : MediaBrowserServiceCompat(), CoroutineScope by MainSco
310312
val navigator = MediaSessionNavigator(publication, publicationId, getMediaSession(context, serviceClass).controller)
311313
pendingNavigator.trySend(PendingNavigator(
312314
navigator = navigator,
313-
media = PendingMedia(publication, publicationId, locator = initialLocator ?: publication.readingOrder.first().toLocator())
315+
media = PendingMedia(
316+
publication = publication,
317+
publicationId = publicationId,
318+
locator = initialLocator
319+
?: requireNotNull(publication.locatorFromLink(publication.readingOrder.first()))
320+
)
314321
))
315322

316323
return navigator

readium/navigator/src/main/java/org/readium/r2/navigator/media/MediaSessionNavigator.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ class MediaSessionNavigator(
158158
private suspend fun createLocator(position: Duration?, metadata: MediaMetadataCompat?): Locator? {
159159
val href = metadata?.resourceHref ?: return null
160160
val index = publication.readingOrder.indexOfFirstWithHref(href) ?: return null
161-
var locator = publication.readingOrder[index].toLocator()
161+
var locator = publication.locatorFromLink(publication.readingOrder[index]) ?: return null
162162

163163
if (position != null) {
164164
val startPosition = durations.slice(0 until index).sum()
@@ -186,8 +186,10 @@ class MediaSessionNavigator(
186186
return true
187187
}
188188

189-
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean =
190-
go(link.toLocator(), animated, completion)
189+
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean {
190+
val locator = publication.locatorFromLink(link) ?: return false
191+
return go(locator, animated, completion)
192+
}
191193

192194
override fun goForward(animated: Boolean, completion: () -> Unit): Boolean {
193195
if (!isActive) return false

readium/navigator/src/main/java/org/readium/r2/navigator/pdf/PdfNavigatorFragment.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,9 @@ class PdfNavigatorFragment internal constructor(
180180
// Navigator
181181

182182
override val currentLocator: StateFlow<Locator> get() = _currentLocator
183-
private val _currentLocator = MutableStateFlow(initialLocator ?: publication.readingOrder.first().toLocator())
183+
private val _currentLocator = MutableStateFlow(initialLocator
184+
?: requireNotNull(publication.locatorFromLink(publication.readingOrder.first()))
185+
)
184186

185187
override fun go(locator: Locator, animated: Boolean, completion: () -> Unit): Boolean {
186188
listener?.onJumpToLocator(locator)
@@ -189,8 +191,10 @@ class PdfNavigatorFragment internal constructor(
189191
return goToHref(locator.href, pageNumberToIndex(pageNumber), animated, completion)
190192
}
191193

192-
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean =
193-
go(link.toLocator(), animated = animated, completion = completion)
194+
override fun go(link: Link, animated: Boolean, completion: () -> Unit): Boolean {
195+
val locator = publication.locatorFromLink(link) ?: return false
196+
return go(locator, animated, completion)
197+
}
194198

195199
override fun goForward(animated: Boolean, completion: () -> Unit): Boolean {
196200
val page = pageIndexToNumber(pdfView.currentPage)

readium/shared/src/main/java/org/readium/r2/shared/publication/Locator.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ data class Locator(
200200
/**
201201
* Creates a [Locator] from a reading order [Link].
202202
*/
203+
@Deprecated("This may create an incorrect `Locator` if the link `type` is missing. Use `publication.locatorFromLink()` instead.")
203204
fun Link.toLocator(): Locator {
204205
val components = href.split("#", limit = 2)
205206
return Locator(

readium/shared/src/main/java/org/readium/r2/shared/publication/Manifest.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,38 @@ data class Manifest(
6464
}
6565
}
6666

67+
/**
68+
* Finds the first [Link] with the given HREF in the manifest's links.
69+
*
70+
* Searches through (in order) [readingOrder], [resources] and [links] recursively following
71+
* alternate and children links.
72+
*
73+
* If there's no match, try again after removing any query parameter and anchor from the
74+
* given [href].
75+
*/
76+
fun linkWithHref(href: String): Link? {
77+
fun List<Link>.deepLinkWithHref(href: String): Link? {
78+
for (l in this) {
79+
if (l.href == href)
80+
return l
81+
else {
82+
l.alternates.deepLinkWithHref(href)?.let { return it }
83+
l.children.deepLinkWithHref(href)?.let { return it }
84+
}
85+
}
86+
return null
87+
}
88+
89+
fun find(href: String): Link? {
90+
return readingOrder.deepLinkWithHref(href)
91+
?: resources.deepLinkWithHref(href)
92+
?: links.deepLinkWithHref(href)
93+
}
94+
95+
return find(href)
96+
?: find(href.takeWhile { it !in "#?" })
97+
}
98+
6799
/**
68100
* Finds the first [Link] with the given relation in the manifest's links.
69101
*/
@@ -78,6 +110,29 @@ data class Manifest(
78110
fun linksWithRel(rel: String): List<Link> =
79111
(readingOrder + resources + links).filterByRel(rel)
80112

113+
/**
114+
* Creates a new [Locator] object from a [Link] to a resource of this manifest.
115+
*
116+
* Returns null if the resource is not found in this manifest.
117+
*/
118+
fun locatorFromLink(link: Link): Locator? {
119+
val components = link.href.split("#", limit = 2)
120+
val href = components.firstOrNull() ?: link.href
121+
val resourceLink = linkWithHref(href) ?: return null
122+
val type = resourceLink.type ?: return null
123+
val fragment = components.getOrNull(1)
124+
125+
return Locator(
126+
href = href,
127+
type = type,
128+
title = resourceLink.title ?: link.title,
129+
locations = Locator.Locations(
130+
fragments = listOfNotNull(fragment),
131+
progression = if (fragment == null) 0.0 else null
132+
)
133+
)
134+
}
135+
81136
/**
82137
* Serializes a [Publication] to its RWPM JSON representation.
83138
*/

0 commit comments

Comments
 (0)