diff --git a/docs/guides/index.md b/docs/guides/index.md index 7b9c1a2526..01bee09211 100644 --- a/docs/guides/index.md +++ b/docs/guides/index.md @@ -1,5 +1,6 @@ # User guides +* [Opening a publication](open-publication.md) * [Extracting the content of a publication](content.md) * [Supporting PDF documents](pdf.md) * [Configuring the Navigator](navigator-preferences.md) diff --git a/docs/guides/open-publication.md b/docs/guides/open-publication.md new file mode 100644 index 0000000000..ccba4a28c3 --- /dev/null +++ b/docs/guides/open-publication.md @@ -0,0 +1,69 @@ +# Opening a publication + +:warning: The described components are is still experimental. + +Readium requires you to instantiate a few components before you can actually open a publication. + +## Constructing an `AssetOpener` + +First, you need to instantiate an `HttpClient` to provide the toolkit the ability to do HTTP requests. +You can use the Readium `DefaultHttpClient` or a custom implementation. In the former case, its callback will +enable you to perform authentication when required. +Then, you can create an `AssetOpener` which will enable you to read content through different schemes and guessing its format. +In addition to an `HttpClient`, the `AssetOpener` constructor takes a `ContentResolver` to support data access through the `content` scheme. + +```kotlin +val httpClient = DefaultHttpClient() + +val assetOpener = AssetOpener(context.contentResolver, httpClient) +``` + +## Constructing a `PublicationOpener` + +The component which can parse an `Asset` giving access to a publication to build a proper `Publication` +object is the `PublicationOpener`. Its constructor requires you to pass in: + +* a `PublicationParser` it delegates the parsing work to. +* a list of `ContentProtection`s which will deal with DRMs. + +The easiest way to get a `PublicationParser` is to use the `DefaultPublicationParser` class. As to +`ContentProtection`s, you can get one to support LCP publications through your `LcpService` if you have one. + +```kotlin +val contentProtections = listOf(lcpService.contentProtection(authentication)) + +val publicationParser = DefaultPublicationParser(context, httpClient, assetOpener, pdfFactory) + +val publicationOpener = PublicationOpener(publicationParser, contentProtections) +``` + +## Bringing the pieces together + +Once you have got an `AssetOpener` and a `PublicationOpener`, you can eventually open a publication as follows: +```kotlin +val asset = assetOpener.open(url, mediaType) + .getOrElse { return error } + +val publication = publicationOpener.open(asset) + .getOrElse { return error } +``` + +Persisting the asset media type on the device can significantly improve performance as it is valuable hint +for the content format, especially in case of remote publications. + +## Extensibility` + +`DefaultPublicationParser` accepts additional parsers. You can also use your own parser list +with `CompositePublicationParser` or implement [PublicationParser] in the way you like. + +`AssetOpener` offers an alternative constructor providing better extensibility in a similar way. +This constructor takes several parameters with different responsibilities. + +* `ResourceFactory` determines which schemes you will be able to access content through. +* `ArchiveOpener` which kinds of archives your `AssetOpener` will be able to open. +* `FormatSniffer` which file formats your `AssetOpener` will be able to identify. + +For each of these components, you can either use the default implementations or implement yours +with the composite pattern. `CompositeResourceFactory`, `CompositeArchiveOpener` and `CompositeFormatSniffer` +provide simple implementations trying every item of a list in turns. + diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/Error.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/Error.kt index bf7a8bc18a..eda5617269 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/Error.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/Error.kt @@ -56,7 +56,7 @@ public fun Error.toDebugDescription(): String = } else { var desc = "${javaClass.nameWithEnclosingClasses()}: $message" cause?.let { cause -> - desc += "\n\n${cause.toDebugDescription()}" + desc += "\n${cause.toDebugDescription()}" } desc } @@ -67,7 +67,7 @@ private fun Throwable.toDebugDescription(): String { desc += message ?: "" desc += "\n" + stackTrace.take(2).joinToString("\n").prependIndent(" ") cause?.let { cause -> - desc += "\n\n${cause.toDebugDescription()}" + desc += "\n${cause.toDebugDescription()}" } return desc } diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetOpener.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetOpener.kt index e5155b71f6..b93e1b790f 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetOpener.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetOpener.kt @@ -6,23 +6,22 @@ package org.readium.r2.shared.util.asset +import android.content.ContentResolver import java.io.File import org.readium.r2.shared.InternalReadiumApi import org.readium.r2.shared.util.AbsoluteUrl import org.readium.r2.shared.util.Error import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.Url -import org.readium.r2.shared.util.file.FileResourceFactory -import org.readium.r2.shared.util.format.DefaultFormatSniffer import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatHints import org.readium.r2.shared.util.format.FormatSniffer import org.readium.r2.shared.util.getOrElse +import org.readium.r2.shared.util.http.HttpClient import org.readium.r2.shared.util.mediatype.MediaType import org.readium.r2.shared.util.resource.Resource import org.readium.r2.shared.util.resource.ResourceFactory import org.readium.r2.shared.util.toUrl -import org.readium.r2.shared.util.zip.ZipArchiveOpener /** * Retrieves an [Asset] instance providing reading access to the resource(s) of an asset stored at @@ -34,11 +33,20 @@ public class AssetOpener( private val archiveOpener: ArchiveOpener ) { public constructor( - resourceFactory: ResourceFactory = FileResourceFactory(), - archiveOpener: ArchiveOpener = ZipArchiveOpener(), - formatSniffer: FormatSniffer = DefaultFormatSniffer() + resourceFactory: ResourceFactory, + archiveOpener: ArchiveOpener, + formatSniffer: FormatSniffer ) : this(AssetSniffer(formatSniffer, archiveOpener), resourceFactory, archiveOpener) + public constructor( + contentResolver: ContentResolver, + httpClient: HttpClient + ) : this( + DefaultResourceFactory(contentResolver, httpClient), + DefaultArchiveOpener(), + DefaultFormatSniffer() + ) + public sealed class OpenError( override val message: String, override val cause: Error? diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetSniffer.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetSniffer.kt index 0daab59738..b7a6946dbc 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetSniffer.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/asset/AssetSniffer.kt @@ -16,7 +16,6 @@ import org.readium.r2.shared.util.data.Container import org.readium.r2.shared.util.data.ReadError import org.readium.r2.shared.util.data.Readable import org.readium.r2.shared.util.file.FileResource -import org.readium.r2.shared.util.format.DefaultFormatSniffer import org.readium.r2.shared.util.format.Format import org.readium.r2.shared.util.format.FormatHints import org.readium.r2.shared.util.format.FormatSniffer diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/asset/Defaults.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/asset/Defaults.kt new file mode 100644 index 0000000000..93e11be884 --- /dev/null +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/asset/Defaults.kt @@ -0,0 +1,94 @@ +/* + * Copyright 2023 Readium Foundation. All rights reserved. + * Use of this source code is governed by the BSD-style license + * available in the top-level LICENSE file of the project. + */ + +package org.readium.r2.shared.util.asset + +import android.content.ContentResolver +import org.readium.r2.shared.util.content.ContentResourceFactory +import org.readium.r2.shared.util.file.FileResourceFactory +import org.readium.r2.shared.util.format.ArchiveSniffer +import org.readium.r2.shared.util.format.AudioSniffer +import org.readium.r2.shared.util.format.BitmapSniffer +import org.readium.r2.shared.util.format.CompositeFormatSniffer +import org.readium.r2.shared.util.format.EpubDrmSniffer +import org.readium.r2.shared.util.format.EpubSniffer +import org.readium.r2.shared.util.format.FormatSniffer +import org.readium.r2.shared.util.format.HtmlSniffer +import org.readium.r2.shared.util.format.JsonSniffer +import org.readium.r2.shared.util.format.LcpLicenseSniffer +import org.readium.r2.shared.util.format.LpfSniffer +import org.readium.r2.shared.util.format.Opds1Sniffer +import org.readium.r2.shared.util.format.Opds2Sniffer +import org.readium.r2.shared.util.format.PdfSniffer +import org.readium.r2.shared.util.format.RarSniffer +import org.readium.r2.shared.util.format.RpfSniffer +import org.readium.r2.shared.util.format.RwpmSniffer +import org.readium.r2.shared.util.format.W3cWpubSniffer +import org.readium.r2.shared.util.format.ZipSniffer +import org.readium.r2.shared.util.http.HttpClient +import org.readium.r2.shared.util.http.HttpResourceFactory +import org.readium.r2.shared.util.resource.CompositeResourceFactory +import org.readium.r2.shared.util.resource.ResourceFactory +import org.readium.r2.shared.util.zip.ZipArchiveOpener + +/** + * Default implementation of [ResourceFactory] supporting file, content and http schemes. + * + * @param contentResolver content resolver to use to support content scheme. + * @param httpClient Http client to use to support http scheme. + * @param additionalFactories Additional [ResourceFactory] to support additional schemes. + */ +public class DefaultResourceFactory( + contentResolver: ContentResolver, + httpClient: HttpClient, + additionalFactories: List = emptyList() +) : ResourceFactory by CompositeResourceFactory( + *additionalFactories.toTypedArray(), + FileResourceFactory(), + ContentResourceFactory(contentResolver), + HttpResourceFactory(httpClient) +) + +/** + * Default implementation of [ArchiveOpener] supporting only ZIP archives. + * + * @param additionalOpeners Additional openers to be used. + */ +public class DefaultArchiveOpener( + additionalOpeners: List = emptyList() +) : ArchiveOpener by CompositeArchiveOpener( + *additionalOpeners.toTypedArray(), + ZipArchiveOpener() +) + +/** + * Default implementation of [FormatSniffer] guessing as well as possible all formats known by + * Readium. + * + * @param additionalSniffers Additional sniffers to be used to guess content format. + */ +public class DefaultFormatSniffer( + additionalSniffers: List = emptyList() +) : FormatSniffer by CompositeFormatSniffer( + *additionalSniffers.toTypedArray(), + ZipSniffer, + RarSniffer, + EpubSniffer, + LpfSniffer, + ArchiveSniffer, + RpfSniffer, + PdfSniffer, + HtmlSniffer, + BitmapSniffer, + AudioSniffer, + JsonSniffer, + Opds1Sniffer, + Opds2Sniffer, + LcpLicenseSniffer, + EpubDrmSniffer, + W3cWpubSniffer, + RwpmSniffer +) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/format/DefaultSniffers.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/format/Sniffers.kt similarity index 98% rename from readium/shared/src/main/java/org/readium/r2/shared/util/format/DefaultSniffers.kt rename to readium/shared/src/main/java/org/readium/r2/shared/util/format/Sniffers.kt index 8ea076f779..9138a425da 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/format/DefaultSniffers.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/format/Sniffers.kt @@ -29,27 +29,6 @@ import org.readium.r2.shared.util.getOrDefault import org.readium.r2.shared.util.getOrElse import org.readium.r2.shared.util.mediatype.MediaType -public class DefaultFormatSniffer : - FormatSniffer by CompositeFormatSniffer( - ZipSniffer, - RarSniffer, - EpubSniffer, - LpfSniffer, - ArchiveSniffer, - RpfSniffer, - PdfSniffer, - HtmlSniffer, - BitmapSniffer, - AudioSniffer, - JsonSniffer, - Opds1Sniffer, - Opds2Sniffer, - LcpLicenseSniffer, - EpubDrmSniffer, - W3cWpubSniffer, - RwpmSniffer - ) - /** Sniffs an HTML or XHTML document. */ public object HtmlSniffer : FormatSniffer { override fun sniffHints( diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/http/DefaultHttpClient.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/http/DefaultHttpClient.kt index de84072498..8f384052aa 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/http/DefaultHttpClient.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/http/DefaultHttpClient.kt @@ -17,6 +17,7 @@ import java.net.NoRouteToHostException import java.net.SocketTimeoutException import java.net.URL import java.net.UnknownHostException +import javax.net.ssl.SSLHandshakeException import kotlin.time.Duration import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -327,6 +328,8 @@ private fun wrap(cause: IOException): HttpError = HttpError.Unreachable(ThrowableError(cause)) is SocketTimeoutException -> HttpError.Timeout(ThrowableError(cause)) + is SSLHandshakeException -> + HttpError.SslHandshake(ThrowableError(cause)) else -> HttpError.IO(cause) } diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpError.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpError.kt index 23ba24bcca..7015486816 100644 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpError.kt +++ b/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpError.kt @@ -21,6 +21,7 @@ public sealed class HttpError( public override val cause: Error? = null ) : AccessError { + /** Malformed HTTP response. */ public class MalformedResponse(cause: Error?) : HttpError("The received response could not be decoded.", cause) @@ -28,12 +29,18 @@ public sealed class HttpError( public class Timeout(cause: Error) : HttpError("Request timed out.", cause) + /** Server could not be reached. */ public class Unreachable(cause: Error) : HttpError("Server could not be reached.", cause) + /** Redirection failed. */ public class Redirection(cause: Error) : HttpError("Redirection failed.", cause) + /** SSL Handshake failed. */ + public class SslHandshake(cause: Error) : + HttpError("SSL handshake failed.", cause) + /** An unknown networking error. */ public class IO(cause: Error) : HttpError("An IO error occurred.", cause) { @@ -42,6 +49,8 @@ public sealed class HttpError( } /** + * Server responded with an error status code. + * * @param status HTTP status code. * @param mediaType Response media type. * @param body Response body. diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpURLConnectionExt.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpURLConnectionExt.kt deleted file mode 100644 index c203773cb2..0000000000 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/http/HttpURLConnectionExt.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2023 Readium Foundation. All rights reserved. - * Use of this source code is governed by the BSD-style license - * available in the top-level LICENSE file of the project. - */ - -package org.readium.r2.shared.util.http - -import java.net.HttpURLConnection -import org.readium.r2.shared.extensions.extension -import org.readium.r2.shared.util.mediatype.MediaTypeHints - -public operator fun MediaTypeHints.Companion.invoke( - connection: HttpURLConnection, - mediaType: String? = null -): MediaTypeHints = - MediaTypeHints( - mediaTypes = listOfNotNull(connection.contentType, mediaType), - fileExtensions = listOfNotNull( - connection.url.extension - // TODO: The suggested filename extension, part of the HTTP header `Content-Disposition`. - ) - ) diff --git a/readium/shared/src/main/java/org/readium/r2/shared/util/mediatype/MediaTypeHints.kt b/readium/shared/src/main/java/org/readium/r2/shared/util/mediatype/MediaTypeHints.kt deleted file mode 100644 index 22d905db11..0000000000 --- a/readium/shared/src/main/java/org/readium/r2/shared/util/mediatype/MediaTypeHints.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2023 Readium Foundation. All rights reserved. - * Use of this source code is governed by the BSD-style license - * available in the top-level LICENSE file of the project. - */ - -package org.readium.r2.shared.util.mediatype - -import java.nio.charset.Charset - -/** - * Bundle of media type and file extension hints for the [MediaTypeSniffer]. - */ -public data class MediaTypeHints( - val mediaTypes: List = emptyList(), - val fileExtensions: List = emptyList() -) { - public companion object { - public operator fun invoke(mediaType: MediaType? = null, fileExtension: String? = null): MediaTypeHints = - MediaTypeHints( - mediaTypes = listOfNotNull(mediaType), - fileExtensions = listOfNotNull(fileExtension) - ) - - public operator fun invoke( - mediaTypes: List = emptyList(), - fileExtensions: List = emptyList() - ): MediaTypeHints = - MediaTypeHints(mediaTypes.mapNotNull { MediaType(it) }, fileExtensions = fileExtensions) - } - - public operator fun plus(other: MediaTypeHints): MediaTypeHints = - MediaTypeHints( - mediaTypes = mediaTypes + other.mediaTypes, - fileExtensions = fileExtensions + other.fileExtensions - ) - - /** - * Returns a new [MediaTypeHints] after appending the given [fileExtension] hint. - */ - public fun addFileExtension(fileExtension: String?): MediaTypeHints { - fileExtension ?: return this - return copy(fileExtensions = fileExtensions + fileExtension) - } - - /** Finds the first [Charset] declared in the media types' `charset` parameter. */ - public val charset: Charset? get() = - mediaTypes.firstNotNullOfOrNull { it.charset } - - /** Returns whether this context has any of the given file extensions, ignoring case. */ - public fun hasFileExtension(vararg fileExtensions: String): Boolean { - val fileExtensionsHints = this.fileExtensions.map { it.lowercase() } - for (fileExtension in fileExtensions.map { it.lowercase() }) { - if (fileExtensionsHints.contains(fileExtension)) { - return true - } - } - return false - } - - /** - * Returns whether this context has any of the given media type, ignoring case and extra - * parameters. - * - * Implementation note: Use [MediaType] to handle the comparison to avoid edge cases. - */ - public fun hasMediaType(vararg mediaTypes: String): Boolean { - @Suppress("NAME_SHADOWING") - val mediaTypes = mediaTypes.mapNotNull { MediaType(it) } - for (mediaType in mediaTypes) { - if (this.mediaTypes.any { mediaType.contains(it) }) { - return true - } - } - return false - } -} diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/PublicationOpener.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/PublicationOpener.kt index 597daf2a30..98b04b52a0 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/PublicationOpener.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/PublicationOpener.kt @@ -21,13 +21,13 @@ import org.readium.r2.streamer.parser.PublicationParser /** * Opens a [Publication] from an [Asset]. * - * @param parser Parses the content of a publication [Asset]. + * @param publicationParser Parses the content of a publication [Asset]. * @param contentProtections Opens DRM-protected publications. * @param onCreatePublication Called on every parsed [Publication.Builder]. It can be used to modify * the manifest, the root container or the list of service factories of a [Publication]. */ public class PublicationOpener( - private val parser: PublicationParser, + private val publicationParser: PublicationParser, contentProtections: List, private val onCreatePublication: Publication.Builder.() -> Unit = {} ) { @@ -101,7 +101,7 @@ public class PublicationOpener( } } - val builder = parser.parse(transformedAsset, warnings) + val builder = publicationParser.parse(transformedAsset, warnings) .getOrElse { return Try.failure(wrapParserException(it)) } builder.apply { diff --git a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/PublicationParser.kt b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/PublicationParser.kt index 3c2b007a60..1dc9a0da78 100644 --- a/readium/streamer/src/main/java/org/readium/r2/streamer/parser/PublicationParser.kt +++ b/readium/streamer/src/main/java/org/readium/r2/streamer/parser/PublicationParser.kt @@ -62,10 +62,10 @@ public interface PublicationParser { */ public class DefaultPublicationParser( context: Context, - additionalParsers: List = emptyList(), private val httpClient: HttpClient, + assetOpener: AssetOpener, pdfFactory: PdfDocumentFactory<*>?, - assetOpener: AssetOpener + additionalParsers: List = emptyList() ) : PublicationParser by CompositePublicationParser( additionalParsers + listOfNotNull( EpubParser(), diff --git a/test-app/src/main/java/org/readium/r2/testapp/Readium.kt b/test-app/src/main/java/org/readium/r2/testapp/Readium.kt index 0aefee6b64..ca724f339a 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/Readium.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/Readium.kt @@ -17,13 +17,8 @@ import org.readium.r2.shared.ExperimentalReadiumApi import org.readium.r2.shared.util.DebugError import org.readium.r2.shared.util.Try import org.readium.r2.shared.util.asset.AssetOpener -import org.readium.r2.shared.util.content.ContentResourceFactory import org.readium.r2.shared.util.downloads.android.AndroidDownloadManager -import org.readium.r2.shared.util.file.FileResourceFactory import org.readium.r2.shared.util.http.DefaultHttpClient -import org.readium.r2.shared.util.http.HttpResourceFactory -import org.readium.r2.shared.util.resource.CompositeResourceFactory -import org.readium.r2.shared.util.zip.ZipArchiveOpener import org.readium.r2.streamer.PublicationOpener import org.readium.r2.streamer.parser.DefaultPublicationParser @@ -32,26 +27,17 @@ import org.readium.r2.streamer.parser.DefaultPublicationParser */ class Readium(context: Context) { - val httpClient = DefaultHttpClient() + val httpClient = + DefaultHttpClient() - private val archiveOpener = - ZipArchiveOpener() + val assetOpener = + AssetOpener(context.contentResolver, httpClient) - private val resourceFactory = CompositeResourceFactory( - FileResourceFactory(), - ContentResourceFactory(context.contentResolver), - HttpResourceFactory(httpClient) - ) - - val assetOpener = AssetOpener( - resourceFactory, - archiveOpener - ) - - val downloadManager = AndroidDownloadManager( - context = context, - destStorage = AndroidDownloadManager.Storage.App - ) + val downloadManager = + AndroidDownloadManager( + context = context, + destStorage = AndroidDownloadManager.Storage.App + ) /** * The LCP service decrypts LCP-protected publication and acquire publications from a @@ -74,7 +60,7 @@ class Readium(context: Context) { * The PublicationFactory is used to open publications. */ val publicationOpener = PublicationOpener( - parser = DefaultPublicationParser( + publicationParser = DefaultPublicationParser( context, assetOpener = assetOpener, httpClient = httpClient, diff --git a/test-app/src/main/java/org/readium/r2/testapp/domain/ReadUserError.kt b/test-app/src/main/java/org/readium/r2/testapp/domain/ReadUserError.kt index 45abd317d9..639edc7056 100644 --- a/test-app/src/main/java/org/readium/r2/testapp/domain/ReadUserError.kt +++ b/test-app/src/main/java/org/readium/r2/testapp/domain/ReadUserError.kt @@ -32,8 +32,9 @@ fun HttpError.toUserError(): UserError = when (this) { is HttpError.IO -> UserError(R.string.publication_error_network_unexpected) is HttpError.MalformedResponse -> UserError(R.string.publication_error_network_unexpected) is HttpError.Redirection -> UserError(R.string.publication_error_network_unexpected) - is HttpError.Timeout -> UserError(R.string.publication_error_network_connectivity) - is HttpError.Unreachable -> UserError(R.string.publication_error_network_connectivity) + is HttpError.Timeout -> UserError(R.string.publication_error_network_timeout) + is HttpError.Unreachable -> UserError(R.string.publication_error_network_unreachable) + is HttpError.SslHandshake -> UserError(R.string.publication_error_network_ssl_handshake) is HttpError.ErrorResponse -> when (status) { HttpStatus.Forbidden -> UserError(R.string.publication_error_network_forbidden) HttpStatus.NotFound -> UserError(R.string.publication_error_network_not_found) diff --git a/test-app/src/main/res/values/strings.xml b/test-app/src/main/res/values/strings.xml index 0a23b7231e..f48650d47e 100644 --- a/test-app/src/main/res/values/strings.xml +++ b/test-app/src/main/res/values/strings.xml @@ -104,7 +104,9 @@ Asset format is not supported Server denied access to a resource. A resource has not been found on the server. - A connectivity error occurred. + Server cannot be reached. + Server was too long to respond. + A SSL error occurred. An unexpected network error occurred. A file has not been found. An unexpected filesystem error occurred. diff --git a/test-app/src/main/res/xml/network_security_config.xml b/test-app/src/main/res/xml/network_security_config.xml index 4de82b56d6..a16bcd2095 100644 --- a/test-app/src/main/res/xml/network_security_config.xml +++ b/test-app/src/main/res/xml/network_security_config.xml @@ -1,16 +1,8 @@ - - - 127.0.0.1 - localhost - - archive.org -