remove icon size and type

This commit is contained in:
Hunter Kehoe 2022-08-25 21:58:37 -06:00
parent 12bd524cbd
commit a2ae6e4c21
8 changed files with 18 additions and 123 deletions

View file

@ -2,11 +2,11 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 11, "version": 11,
"identityHash": "5a061926458ed65c80431be0a69a2450", "identityHash": "31f8e6a2032d1d404fad4307abf23e1b",
"entities": [ "entities": [
{ {
"tableName": "Subscription", "tableName": "Subscription",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `minPriority` INTEGER NOT NULL, `autoDelete` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, PRIMARY KEY(`id`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `minPriority` INTEGER NOT NULL, `autoDelete` INTEGER NOT NULL, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, PRIMARY KEY(`id`))",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -50,12 +50,6 @@
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
{
"fieldPath": "lastNotificationId",
"columnName": "lastNotificationId",
"affinity": "TEXT",
"notNull": false
},
{ {
"fieldPath": "icon", "fieldPath": "icon",
"columnName": "icon", "columnName": "icon",
@ -73,12 +67,6 @@
"columnName": "upConnectorToken", "columnName": "upConnectorToken",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
},
{
"fieldPath": "displayName",
"columnName": "displayName",
"affinity": "TEXT",
"notNull": false
} }
], ],
"primaryKey": { "primaryKey": {
@ -112,7 +100,7 @@
}, },
{ {
"tableName": "Notification", "tableName": "Notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_type` TEXT, `icon_size` INTEGER, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -187,30 +175,6 @@
"affinity": "INTEGER", "affinity": "INTEGER",
"notNull": true "notNull": true
}, },
{
"fieldPath": "icon.url",
"columnName": "icon_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "icon.type",
"columnName": "icon_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "icon.size",
"columnName": "icon_size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "icon.contentUri",
"columnName": "icon_contentUri",
"affinity": "TEXT",
"notNull": false
},
{ {
"fieldPath": "attachment.name", "fieldPath": "attachment.name",
"columnName": "attachment_name", "columnName": "attachment_name",
@ -350,7 +314,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a061926458ed65c80431be0a69a2450')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '31f8e6a2032d1d404fad4307abf23e1b')"
] ]
} }
} }

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 12, "version": 12,
"identityHash": "5a061926458ed65c80431be0a69a2450", "identityHash": "d230005f4d9824ba9aa34c61003bdcbb",
"entities": [ "entities": [
{ {
"tableName": "Subscription", "tableName": "Subscription",
@ -112,7 +112,7 @@
}, },
{ {
"tableName": "Notification", "tableName": "Notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_type` TEXT, `icon_size` INTEGER, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `icon_url` TEXT, `icon_contentUri` TEXT, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -193,18 +193,6 @@
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": false
}, },
{
"fieldPath": "icon.type",
"columnName": "icon_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "icon.size",
"columnName": "icon_size",
"affinity": "INTEGER",
"notNull": false
},
{ {
"fieldPath": "icon.contentUri", "fieldPath": "icon.contentUri",
"columnName": "icon_contentUri", "columnName": "icon_contentUri",
@ -350,7 +338,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5a061926458ed65c80431be0a69a2450')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd230005f4d9824ba9aa34c61003bdcbb')"
] ]
} }
} }

View file

@ -152,8 +152,6 @@ class Backuper(val context: Context) {
val icon = if (n.icon != null) { val icon = if (n.icon != null) {
io.heckel.ntfy.db.Icon( io.heckel.ntfy.db.Icon(
url = n.icon.url, url = n.icon.url,
type = n.icon.type,
size = n.icon.size,
contentUri = n.icon.contentUri, contentUri = n.icon.contentUri,
) )
} else { } else {
@ -281,8 +279,6 @@ class Backuper(val context: Context) {
val icon = if (n.icon != null) { val icon = if (n.icon != null) {
Icon( Icon(
url = n.icon.url, url = n.icon.url,
type = n.icon.type,
size = n.icon.size,
contentUri = n.icon.contentUri, contentUri = n.icon.contentUri,
) )
} else { } else {
@ -403,10 +399,7 @@ data class Attachment(
data class Icon( data class Icon(
val url: String, // URL (mandatory, see ntfy server) val url: String, // URL (mandatory, see ntfy server)
val type: String?, // MIME type
val size: Long?, // Size in bytes
val contentUri: String?, // After it's downloaded, the content:// location val contentUri: String?, // After it's downloaded, the content:// location
val progress: Int, // Progress during download, -1 if not downloaded
) )
data class User( data class User(

View file

@ -95,12 +95,10 @@ const val ATTACHMENT_PROGRESS_DONE = 100
@Entity @Entity
data class Icon( data class Icon(
@ColumnInfo(name = "url") val url: String, // URL (mandatory, see ntfy server) @ColumnInfo(name = "url") val url: String, // URL (mandatory, see ntfy server)
@ColumnInfo(name = "type") val type: String?, // MIME type
@ColumnInfo(name = "size") val size: Long?, // Size in bytes
@ColumnInfo(name = "contentUri") val contentUri: String?, // After it's downloaded, the content:// location @ColumnInfo(name = "contentUri") val contentUri: String?, // After it's downloaded, the content:// location
) { ) {
constructor(url:String, type: String?, size: Long?) : constructor(url:String) :
this(url, type, size, null) this(url, null)
} }
@Entity @Entity
@ -282,8 +280,6 @@ abstract class Database : RoomDatabase() {
db.execSQL("ALTER TABLE Subscription ADD COLUMN lastNotificationId TEXT") db.execSQL("ALTER TABLE Subscription ADD COLUMN lastNotificationId TEXT")
db.execSQL("ALTER TABLE Subscription ADD COLUMN displayName TEXT") db.execSQL("ALTER TABLE Subscription ADD COLUMN displayName TEXT")
db.execSQL("ALTER TABLE Notification ADD COLUMN icon_url TEXT") // Room limitation: Has to be nullable for @Embedded db.execSQL("ALTER TABLE Notification ADD COLUMN icon_url TEXT") // Room limitation: Has to be nullable for @Embedded
db.execSQL("ALTER TABLE Notification ADD COLUMN icon_type TEXT")
db.execSQL("ALTER TABLE Notification ADD COLUMN icon_size INT")
db.execSQL("ALTER TABLE Notification ADD COLUMN icon_contentUri TEXT") db.execSQL("ALTER TABLE Notification ADD COLUMN icon_contentUri TEXT")
} }
} }

View file

@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit
class DownloadIconWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) { class DownloadIconWorker(private val context: Context, params: WorkerParameters) : Worker(context, params) {
private val client = OkHttpClient.Builder() private val client = OkHttpClient.Builder()
.callTimeout(15, TimeUnit.MINUTES) // Total timeout for entire request .callTimeout(1, TimeUnit.MINUTES) // Total timeout for entire request
.connectTimeout(15, TimeUnit.SECONDS) .connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS) .readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS) .writeTimeout(15, TimeUnit.SECONDS)
@ -69,8 +69,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
if (!response.isSuccessful || response.body == null) { if (!response.isSuccessful || response.body == null) {
throw Exception("Unexpected response: ${response.code}") throw Exception("Unexpected response: ${response.code}")
} }
save(updateIconFromResponse(response)) if (shouldAbortDownload(response)) {
if (shouldAbortDownload()) {
Log.d(TAG, "Aborting download: Content-Length is larger than auto-download setting") Log.d(TAG, "Aborting download: Content-Length is larger than auto-download setting")
return return
} }
@ -85,7 +84,7 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
val downloadLimit = if (repository.getAutoDownloadMaxSize() != Repository.AUTO_DOWNLOAD_NEVER && repository.getAutoDownloadMaxSize() != Repository.AUTO_DOWNLOAD_ALWAYS) { val downloadLimit = if (repository.getAutoDownloadMaxSize() != Repository.AUTO_DOWNLOAD_NEVER && repository.getAutoDownloadMaxSize() != Repository.AUTO_DOWNLOAD_ALWAYS) {
repository.getAutoDownloadMaxSize() repository.getAutoDownloadMaxSize()
} else { } else {
null MAX_ICON_DOWNLOAD_SIZE.toLong()
} }
outFile.use { fileOut -> outFile.use { fileOut ->
val fileIn = response.body!!.byteStream() val fileIn = response.body!!.byteStream()
@ -102,48 +101,14 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
} }
Log.d(TAG, "Icon download: successful response, proceeding with download") Log.d(TAG, "Icon download: successful response, proceeding with download")
save(icon.copy( save(icon.copy(
size = bytesCopied,
contentUri = uri.toString() contentUri = uri.toString()
)) ))
} }
} catch (e: Exception) { } catch (e: Exception) {
failed(e) failed(e)
// Toast in a Worker: https://stackoverflow.com/a/56428145/1440785
val handler = Handler(Looper.getMainLooper())
handler.postDelayed({
Toast
.makeText(context, context.getString(R.string.detail_item_icon_download_failed, e.message), Toast.LENGTH_LONG)
.show()
}, 200)
} }
} }
private fun updateIconFromResponse(response: Response): Icon {
val size = if (response.headers["Content-Length"]?.toLongOrNull() != null) {
Log.d(TAG, "We got the long! icon here")
response.headers["Content-Length"]?.toLong()
} else {
icon.size // May be null!
}
val mimeType = if (response.headers["Content-Type"] != null) {
response.headers["Content-Type"]
} else {
val ext = MimeTypeMap.getFileExtensionFromUrl(icon.url)
if (ext != null) {
val typeFromExt = MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext)
typeFromExt ?: icon.type // May be null!
} else {
icon.type // May be null!
}
}
Log.d(TAG, "New icon size: $size, type: $mimeType")
return icon.copy(
size = size,
type = mimeType
)
}
private fun failed(e: Exception) { private fun failed(e: Exception) {
Log.w(TAG, "Icon download failed", e) Log.w(TAG, "Icon download failed", e)
maybeDeleteFile() maybeDeleteFile()
@ -166,9 +131,9 @@ class DownloadIconWorker(private val context: Context, params: WorkerParameters)
repository.updateNotification(notification) repository.updateNotification(notification)
} }
private fun shouldAbortDownload(): Boolean { private fun shouldAbortDownload(response: Response): Boolean {
val maxAutoDownloadSize = MAX_ICON_DOWNLOAD_SIZE val maxAutoDownloadSize = MAX_ICON_DOWNLOAD_SIZE
val size = icon.size ?: return false // Don't abort if size unknown val size = response.headers["Content-Length"]?.toLongOrNull() ?: return false // Don't abort here if size unknown
return size > maxAutoDownloadSize return size > maxAutoDownloadSize
} }

View file

@ -60,8 +60,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
Log.d(TAG, "Attachment already expired at ${attachment.expires}, not downloading") Log.d(TAG, "Attachment already expired at ${attachment.expires}, not downloading")
return false return false
} }
val maxAutoDownloadSize = repository.getAutoDownloadMaxSize() when (val maxAutoDownloadSize = repository.getAutoDownloadMaxSize()) {
when (maxAutoDownloadSize) {
Repository.AUTO_DOWNLOAD_ALWAYS -> return true Repository.AUTO_DOWNLOAD_ALWAYS -> return true
Repository.AUTO_DOWNLOAD_NEVER -> return false Repository.AUTO_DOWNLOAD_NEVER -> return false
else -> { else -> {
@ -73,15 +72,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
} }
} }
private fun shouldDownloadIcon(notification: Notification): Boolean { private fun shouldDownloadIcon(notification: Notification): Boolean {
if (notification.icon == null) { return notification.icon != null
return false
}
val icon = notification.icon
val maxIconDownloadSize = DownloadIconWorker.MAX_ICON_DOWNLOAD_SIZE
if (icon.size == null) {
return true // DownloadWorker will bail out if attachment is too large!
}
return icon.size <= maxIconDownloadSize
} }
private fun shouldNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean { private fun shouldNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean {

View file

@ -52,9 +52,7 @@ class NotificationParser {
} else null } else null
val icon: Icon? = if (message.icon != null) { val icon: Icon? = if (message.icon != null) {
Icon( Icon(
url = message.icon, url = message.icon
size = null,
type = null
) )
} else null } else null
val notification = Notification( val notification = Notification(

View file

@ -96,7 +96,7 @@ class NotificationService(val context: Context) {
val contentUri = notification.attachment?.contentUri val contentUri = notification.attachment?.contentUri
val isSupportedImage = supportedImage(notification.attachment?.type) val isSupportedImage = supportedImage(notification.attachment?.type)
val subscriptionIcon = if (subscription.icon != null) subscription.icon.readBitmapFromUriOrNull(context) else null val subscriptionIcon = if (subscription.icon != null) subscription.icon.readBitmapFromUriOrNull(context) else null
val notificationIcon = if (notification.icon != null && supportedImage(notification.icon.type)) notification.icon.contentUri?.readBitmapFromUriOrNull(context) else null val notificationIcon = if (notification.icon != null) notification.icon.contentUri?.readBitmapFromUriOrNull(context) else null
val largeIcon = notificationIcon ?: subscriptionIcon val largeIcon = notificationIcon ?: subscriptionIcon
if (contentUri != null && isSupportedImage) { if (contentUri != null && isSupportedImage) {
try { try {