Make large attachments fail fast
This commit is contained in:
parent
9afdf5e6e7
commit
29a40080db
4 changed files with 53 additions and 25 deletions
|
@ -1,15 +1,10 @@
|
||||||
package io.heckel.ntfy.msg
|
package io.heckel.ntfy.msg
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import io.heckel.ntfy.BuildConfig
|
import io.heckel.ntfy.BuildConfig
|
||||||
import io.heckel.ntfy.db.Notification
|
import io.heckel.ntfy.db.Notification
|
||||||
import io.heckel.ntfy.db.User
|
import io.heckel.ntfy.db.User
|
||||||
import io.heckel.ntfy.util.Log
|
import io.heckel.ntfy.util.*
|
||||||
import io.heckel.ntfy.util.topicUrl
|
|
||||||
import io.heckel.ntfy.util.topicUrlAuth
|
|
||||||
import io.heckel.ntfy.util.topicUrlJson
|
|
||||||
import io.heckel.ntfy.util.topicUrlJsonPoll
|
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
@ -24,12 +19,29 @@ class ApiService {
|
||||||
.readTimeout(15, TimeUnit.SECONDS)
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
.writeTimeout(15, TimeUnit.SECONDS)
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
private val publishClient = OkHttpClient.Builder()
|
||||||
|
.callTimeout(5, TimeUnit.MINUTES) // Total timeout for entire request
|
||||||
|
.connectTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(15, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
private val subscriberClient = OkHttpClient.Builder()
|
private val subscriberClient = OkHttpClient.Builder()
|
||||||
.readTimeout(77, TimeUnit.SECONDS) // Assuming that keepalive messages are more frequent than this
|
.readTimeout(77, TimeUnit.SECONDS) // Assuming that keepalive messages are more frequent than this
|
||||||
.build()
|
.build()
|
||||||
private val parser = NotificationParser()
|
private val parser = NotificationParser()
|
||||||
|
|
||||||
fun publish(baseUrl: String, topic: String, user: User?, message: String, title: String, priority: Int, tags: List<String>, delay: String, body: RequestBody? = null, filename: String = "") {
|
fun publish(
|
||||||
|
baseUrl: String,
|
||||||
|
topic: String,
|
||||||
|
user: User? = null,
|
||||||
|
message: String,
|
||||||
|
title: String = "",
|
||||||
|
priority: Int = 3,
|
||||||
|
tags: List<String> = emptyList(),
|
||||||
|
delay: String = "",
|
||||||
|
body: RequestBody? = null,
|
||||||
|
filename: String = ""
|
||||||
|
) {
|
||||||
val url = topicUrl(baseUrl, topic)
|
val url = topicUrl(baseUrl, topic)
|
||||||
Log.d(TAG, "Publishing to $url")
|
Log.d(TAG, "Publishing to $url")
|
||||||
|
|
||||||
|
@ -56,11 +68,14 @@ class ApiService {
|
||||||
} else {
|
} else {
|
||||||
builder.put(message.toRequestBody())
|
builder.put(message.toRequestBody())
|
||||||
}
|
}
|
||||||
client.newCall(builder.build()).execute().use { response ->
|
val request = builder.build()
|
||||||
|
Log.d(TAG, request.toString())
|
||||||
|
publishClient.newCall(request).execute().use { response ->
|
||||||
if (response.code == 401 || response.code == 403) {
|
if (response.code == 401 || response.code == 403) {
|
||||||
throw UnauthorizedException(user)
|
throw UnauthorizedException(user)
|
||||||
}
|
} else if (response.code == 413) {
|
||||||
if (!response.isSuccessful) {
|
throw EntityTooLargeException()
|
||||||
|
} else if (!response.isSuccessful) {
|
||||||
throw Exception("Unexpected response ${response.code} when publishing to $url")
|
throw Exception("Unexpected response ${response.code} when publishing to $url")
|
||||||
}
|
}
|
||||||
Log.d(TAG, "Successfully published to $url")
|
Log.d(TAG, "Successfully published to $url")
|
||||||
|
@ -149,6 +164,7 @@ class ApiService {
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnauthorizedException(val user: User?) : Exception()
|
class UnauthorizedException(val user: User?) : Exception()
|
||||||
|
class EntityTooLargeException() : Exception()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})"
|
val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})"
|
||||||
|
|
|
@ -177,16 +177,12 @@ class ShareActivity : AppCompatActivity() {
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
val user = repository.getUser(baseUrl)
|
val user = repository.getUser(baseUrl)
|
||||||
try {
|
try {
|
||||||
val filename = if (fileUri != null) {
|
val (filename, body) = if (fileUri != null) {
|
||||||
fileStat(this@ShareActivity, fileUri).filename
|
val stat = fileStat(this@ShareActivity, fileUri)
|
||||||
|
val body = ContentUriRequestBody(applicationContext.contentResolver, fileUri!!, stat.size)
|
||||||
|
Pair(stat.filename, body)
|
||||||
} else {
|
} else {
|
||||||
""
|
Pair("", null)
|
||||||
}
|
|
||||||
val body = if (fileUri != null) {
|
|
||||||
val resolver = applicationContext.contentResolver
|
|
||||||
ContentUriRequestBody(resolver, fileUri!!)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
}
|
||||||
api.publish(
|
api.publish(
|
||||||
baseUrl = baseUrl,
|
baseUrl = baseUrl,
|
||||||
|
@ -198,7 +194,7 @@ class ShareActivity : AppCompatActivity() {
|
||||||
tags = emptyList(),
|
tags = emptyList(),
|
||||||
delay = "",
|
delay = "",
|
||||||
body = body, // May be null
|
body = body, // May be null
|
||||||
filename = filename // May be empty
|
filename = filename, // May be empty
|
||||||
)
|
)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
finish()
|
finish()
|
||||||
|
@ -207,9 +203,20 @@ class ShareActivity : AppCompatActivity() {
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
val message = if (e is ApiService.UnauthorizedException) {
|
||||||
|
if (e.user != null) {
|
||||||
|
getString(R.string.detail_test_message_error_unauthorized_user, e.user.username)
|
||||||
|
} else {
|
||||||
|
getString(R.string.detail_test_message_error_unauthorized_anon)
|
||||||
|
}
|
||||||
|
} else if (e is ApiService.EntityTooLargeException) {
|
||||||
|
getString(R.string.detail_test_message_error_too_large)
|
||||||
|
} else {
|
||||||
|
getString(R.string.detail_test_message_error, e.message)
|
||||||
|
}
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
progress.visibility = View.GONE
|
progress.visibility = View.GONE
|
||||||
errorText.text = e.message
|
errorText.text = message
|
||||||
errorImage.visibility = View.VISIBLE
|
errorImage.visibility = View.VISIBLE
|
||||||
errorText.visibility = View.VISIBLE
|
errorText.visibility = View.VISIBLE
|
||||||
}
|
}
|
||||||
|
|
|
@ -253,15 +253,19 @@ fun isDarkThemeOn(context: Context): Boolean {
|
||||||
|
|
||||||
// https://cketti.de/2020/05/23/content-uris-and-okhttp/
|
// https://cketti.de/2020/05/23/content-uris-and-okhttp/
|
||||||
class ContentUriRequestBody(
|
class ContentUriRequestBody(
|
||||||
private val contentResolver: ContentResolver,
|
private val resolver: ContentResolver,
|
||||||
private val contentUri: Uri
|
private val uri: Uri,
|
||||||
|
private val size: Long
|
||||||
) : RequestBody() {
|
) : RequestBody() {
|
||||||
|
override fun contentLength(): Long {
|
||||||
|
return size
|
||||||
|
}
|
||||||
override fun contentType(): MediaType? {
|
override fun contentType(): MediaType? {
|
||||||
val contentType = contentResolver.getType(contentUri)
|
val contentType = resolver.getType(uri)
|
||||||
return contentType?.toMediaTypeOrNull()
|
return contentType?.toMediaTypeOrNull()
|
||||||
}
|
}
|
||||||
override fun writeTo(sink: BufferedSink) {
|
override fun writeTo(sink: BufferedSink) {
|
||||||
val inputStream = contentResolver.openInputStream(contentUri) ?: throw IOException("Couldn't open content URI for reading")
|
val inputStream = resolver.openInputStream(uri) ?: throw IOException("Couldn't open content URI for reading")
|
||||||
inputStream.source().use { source ->
|
inputStream.source().use { source ->
|
||||||
sink.writeAll(source)
|
sink.writeAll(source)
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,7 @@
|
||||||
<string name="detail_test_message_error">Cannot send message: %1$s</string>
|
<string name="detail_test_message_error">Cannot send message: %1$s</string>
|
||||||
<string name="detail_test_message_error_unauthorized_anon">Cannot send message: Anonymous publishing not allowed</string>
|
<string name="detail_test_message_error_unauthorized_anon">Cannot send message: Anonymous publishing not allowed</string>
|
||||||
<string name="detail_test_message_error_unauthorized_user">Cannot send message: User %1$s not authorized</string>
|
<string name="detail_test_message_error_unauthorized_user">Cannot send message: User %1$s not authorized</string>
|
||||||
|
<string name="detail_test_message_error_too_large">Cannot send message: Attachment too large</string>
|
||||||
<string name="detail_copied_to_clipboard_message">Copied to clipboard</string>
|
<string name="detail_copied_to_clipboard_message">Copied to clipboard</string>
|
||||||
<string name="detail_instant_delivery_enabled">Instant delivery enabled</string>
|
<string name="detail_instant_delivery_enabled">Instant delivery enabled</string>
|
||||||
<string name="detail_instant_delivery_disabled">Instant delivery disabled</string>
|
<string name="detail_instant_delivery_disabled">Instant delivery disabled</string>
|
||||||
|
|
Loading…
Reference in a new issue