Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Various bugfixes #405

Merged
merged 13 commits into from
Oct 13, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import org.readium.r2.shared.util.Url
import org.readium.r2.shared.util.pdf.cachedIn
import org.readium.r2.shared.util.resource.Resource
import org.readium.r2.shared.util.resource.ResourceTry
import timber.log.Timber

@ExperimentalReadiumApi
public class PsPdfKitDocumentFragment internal constructor(
Expand Down Expand Up @@ -162,7 +163,7 @@ public class PsPdfKitDocumentFragment internal constructor(
* Recreates the [PdfFragment] with the current settings.
*/
private fun resetPdfFragment() {
if (view == null) return
if (isStateSaved || view == null) return
val doc = viewModel.document ?: return

doc.document.pageBinding = settings.readingProgression.pageBinding
Expand Down Expand Up @@ -295,9 +296,15 @@ public class PsPdfKitDocumentFragment internal constructor(
}

override fun onDocumentLoaded(document: PdfDocument) {
super.onDocumentLoaded(document)
val index = pageIndex.value
if (index < 0 || index >= document.pageCount) {
Timber.w(
"Tried to restore page index $index, but the document has ${document.pageCount} pages"
)
return
}

checkNotNull(pdfFragment).setPageIndex(pageIndex.value, false)
checkNotNull(pdfFragment).setPageIndex(index, false)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,10 @@ internal class LcpContentProtection(
LicenseDocument(it)
} catch (e: Exception) {
return Try.failure(
Publication.OpenError.InvalidAsset(cause = ThrowableError(e))
Publication.OpenError.InvalidAsset(
"Failed to read the LCP license document",
cause = ThrowableError(e)
)
)
}
}
Expand All @@ -127,9 +130,7 @@ internal class LcpContentProtection(
val url = (link.url() as? AbsoluteUrl)
?: return Try.failure(
Publication.OpenError.InvalidAsset(
cause = ThrowableError(
LcpException.Parsing.Url(rel = LicenseDocument.Rel.Publication.value)
)
"The LCP license document does not contain a valid link to the publication"
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import kotlinx.coroutines.launch
import org.readium.r2.lcp.license.container.createLicenseContainer
import org.readium.r2.lcp.license.model.LicenseDocument
import org.readium.r2.shared.extensions.tryOrLog
import org.readium.r2.shared.util.ErrorException
import org.readium.r2.shared.util.downloads.DownloadManager
import org.readium.r2.shared.util.mediatype.FormatRegistry
import org.readium.r2.shared.util.mediatype.MediaType
Expand Down Expand Up @@ -255,7 +256,7 @@ public class LcpPublicationRetriever(
listenersForId.forEach {
it.onAcquisitionFailed(
lcpRequestId,
LcpException.Network(Exception(error.message))
LcpException.Network(ErrorException(error))
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ public class TtsNavigator<S : TtsEngine.Settings, P : TtsEngine.Preferences<P>,

val contentIterator =
TtsUtteranceIterator(publication, tokenizerFactory, initialLocator)
if (!contentIterator.hasNext()) {
return null
}

val ttsEngine =
ttsEngineProvider.createEngine(publication, actualInitialPreferences)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class TtsNavigatorFactory<S : TtsEngine.Settings, P : TtsEngine.Preferenc
) {
public companion object {

public suspend operator fun invoke(
public operator fun invoke(
application: Application,
publication: Publication,
tokenizerFactory: (language: Language?) -> TextTokenizer = defaultTokenizerFactory,
Expand All @@ -57,35 +57,31 @@ public class TtsNavigatorFactory<S : TtsEngine.Settings, P : TtsEngine.Preferenc
)
}

public suspend operator fun <S : TtsEngine.Settings, P : TtsEngine.Preferences<P>, E : PreferencesEditor<P>,
public operator fun <S : TtsEngine.Settings, P : TtsEngine.Preferences<P>, E : PreferencesEditor<P>,
F : TtsEngine.Error, V : TtsEngine.Voice> invoke(
application: Application,
publication: Publication,
ttsEngineProvider: TtsEngineProvider<S, P, E, F, V>,
tokenizerFactory: (language: Language?) -> TextTokenizer = defaultTokenizerFactory,
metadataProvider: MediaMetadataProvider = defaultMediaMetadataProvider
): TtsNavigatorFactory<S, P, E, F, V>? {
return createNavigatorFactory(
): TtsNavigatorFactory<S, P, E, F, V>? =
createNavigatorFactory(
application,
publication,
ttsEngineProvider,
tokenizerFactory,
metadataProvider
)
}

private suspend fun <S : TtsEngine.Settings, P : TtsEngine.Preferences<P>, E : PreferencesEditor<P>,
private fun <S : TtsEngine.Settings, P : TtsEngine.Preferences<P>, E : PreferencesEditor<P>,
F : TtsEngine.Error, V : TtsEngine.Voice> createNavigatorFactory(
application: Application,
publication: Publication,
ttsEngineProvider: TtsEngineProvider<S, P, E, F, V>,
tokenizerFactory: (language: Language?) -> TextTokenizer,
metadataProvider: MediaMetadataProvider
): TtsNavigatorFactory<S, P, E, F, V>? {
publication.content()
?.iterator()
?.takeIf { it.hasNext() }
?: return null
publication.content() ?: return null

return TtsNavigatorFactory(
application,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,19 @@ internal class TtsUtteranceIterator(
private fun createIterator(locator: Locator?): Content.Iterator =
contentService.content(locator).iterator()

suspend fun hasPrevious(): Boolean =
hasNextIn(Direction.Backward)

suspend fun hasNext(): Boolean =
hasNextIn(Direction.Forward)

private suspend fun hasNextIn(direction: Direction): Boolean {
if (utterances.isEmpty()) {
loadNextUtterances(direction)
}
return utterances.hasNextIn(direction)
}

/**
* Advances to the previous item and returns it, or null if we reached the beginning.
*/
Expand Down Expand Up @@ -217,6 +230,12 @@ internal class TtsUtteranceIterator(
}
}

private fun <E> CursorList<E>.hasNextIn(direction: Direction): Boolean =
when (direction) {
Direction.Forward -> hasNext()
Direction.Backward -> hasPrevious()
}

private fun <E> CursorList<E>.nextIn(direction: Direction): E? =
when (direction) {
Direction.Forward -> if (hasNext()) next() else null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import org.readium.r2.shared.publication.services.PositionsService
import org.readium.r2.shared.publication.services.WebPositionsService
import org.readium.r2.shared.publication.services.content.ContentService
import org.readium.r2.shared.publication.services.search.SearchService
import org.readium.r2.shared.util.BaseError
import org.readium.r2.shared.util.Closeable
import org.readium.r2.shared.util.Error
import org.readium.r2.shared.util.ThrowableError
Expand Down Expand Up @@ -497,8 +496,10 @@ public class Publication(
/**
* Errors occurring while opening a Publication.
*/
public sealed class OpenError(message: String, cause: Error? = null) :
BaseError(message, cause) {
public sealed class OpenError(
override val message: String,
override val cause: Error? = null
) : Error {

/**
* The file format could not be recognized by any parser.
Expand All @@ -514,12 +515,11 @@ public class Publication(
/**
* The publication parsing failed with the given underlying error.
*/
public class InvalidAsset private constructor(
public class InvalidAsset(
message: String,
cause: Error?
cause: Error? = null
) : OpenError(message, cause) {
public constructor(message: String) : this(message, null)
public constructor(cause: Error? = null) : this(
public constructor(cause: Error?) : this(
"The asset seems corrupted so the publication cannot be opened.",
cause
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

package org.readium.r2.shared.publication.services.content.iterators

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
import org.jsoup.nodes.Element
import org.jsoup.nodes.Node
Expand All @@ -14,6 +16,7 @@ import org.jsoup.parser.Parser
import org.jsoup.select.NodeTraversor
import org.jsoup.select.NodeVisitor
import org.readium.r2.shared.ExperimentalReadiumApi
import org.readium.r2.shared.extensions.tryOrLog
import org.readium.r2.shared.extensions.tryOrNull
import org.readium.r2.shared.publication.Link
import org.readium.r2.shared.publication.Locator
Expand Down Expand Up @@ -146,43 +149,44 @@ public class HtmlResourceContentIterator internal constructor(

private var parsedElements: ParsedElements? = null

private suspend fun parseElements(): ParsedElements {
val document = resource.use { res ->
val html = res.readAsString().getOrElse {
Timber.w(it, "Failed to read HTML resource")
return ParsedElements()
private suspend fun parseElements(): ParsedElements =
withContext(Dispatchers.Default) {
val document = resource.use { res ->
val html = res.readAsString().getOrElse {
Timber.w(it, "Failed to read HTML resource")
return@withContext ParsedElements()
}

Jsoup.parse(html)
}

Jsoup.parse(html)
}
val contentParser = ContentParser(
baseLocator = locator,
startElement = locator.locations.cssSelector?.let {
tryOrNull { document.selectFirst(it) }
},
beforeMaxLength = beforeMaxLength
)
NodeTraversor.traverse(contentParser, document.body())
val elements = contentParser.result()
val elementCount = elements.elements.size
if (elementCount == 0) {
return@withContext elements
}

val contentParser = ContentParser(
baseLocator = locator,
startElement = locator.locations.cssSelector?.let {
tryOrNull { document.selectFirst(it) }
},
beforeMaxLength = beforeMaxLength
)
NodeTraversor.traverse(contentParser, document.body())
val elements = contentParser.result()
val elementCount = elements.elements.size
if (elementCount == 0) {
return elements
elements.copy(
elements = elements.elements.mapIndexed { index, element ->
val progression = index.toDouble() / elementCount
element.copy(
progression = progression,
totalProgression = totalProgressionRange?.let {
totalProgressionRange.start + progression * (totalProgressionRange.endInclusive - totalProgressionRange.start)
}
)
}
)
}

return elements.copy(
elements = elements.elements.mapIndexed { index, element ->
val progression = index.toDouble() / elementCount
element.copy(
progression = progression,
totalProgression = totalProgressionRange?.let {
totalProgressionRange.start + progression * (totalProgressionRange.endInclusive - totalProgressionRange.start)
}
)
}
)
}

private fun Content.Element.copy(progression: Double?, totalProgression: Double?): Content.Element {
fun Locator.update(): Locator =
copyWithLocations(
Expand Down Expand Up @@ -256,9 +260,12 @@ public class HtmlResourceContentIterator internal constructor(

private data class ParentElement(
val element: Element,
val cssSelector: String
val cssSelector: String?
) {
constructor(element: Element) : this(element, element.cssSelector())
constructor(element: Element) : this(
element = element,
cssSelector = tryOrLog { element.cssSelector() }
)
}

override fun head(node: Node, depth: Int) {
Expand All @@ -275,7 +282,9 @@ public class HtmlResourceContentIterator internal constructor(
baseLocator.copy(
locations = Locator.Locations(
otherLocations = buildMap {
put("cssSelector", parent.cssSelector as Any)
parent.cssSelector?.let {
put("cssSelector", it as Any)
}
}
)
)
Expand Down Expand Up @@ -402,8 +411,8 @@ public class HtmlResourceContentIterator internal constructor(
locator = baseLocator.copy(
locations = Locator.Locations(
otherLocations = buildMap {
parent?.let {
put("cssSelector", it.cssSelector as Any)
parent?.cssSelector?.let {
put("cssSelector", it as Any)
}
}
),
Expand Down Expand Up @@ -442,8 +451,8 @@ public class HtmlResourceContentIterator internal constructor(
locator = baseLocator.copy(
locations = Locator.Locations(
otherLocations = buildMap {
parent?.let {
put("cssSelector", it.cssSelector as Any)
parent?.cssSelector?.let {
put("cssSelector", it as Any)
}
}
),
Expand Down
Loading