From 5a6d45d8106023ce0b00ece2e8f27455caccb60b Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Tue, 11 Jan 2022 19:37:34 -0500 Subject: [PATCH] Auto-download max size --- .../java/io/heckel/ntfy/data/Repository.kt | 20 +++++---- .../java/io/heckel/ntfy/msg/ApiService.kt | 7 +--- .../java/io/heckel/ntfy/msg/DownloadWorker.kt | 1 + .../heckel/ntfy/msg/NotificationDispatcher.kt | 22 +++++++--- .../heckel/ntfy/service/SubscriberService.kt | 4 +- .../io/heckel/ntfy/ui/SettingsActivity.kt | 42 +++++++++++-------- app/src/main/java/io/heckel/ntfy/util/Util.kt | 7 ++-- app/src/main/res/values/strings.xml | 13 +++++- app/src/main/res/values/values.xml | 20 +++++++++ app/src/main/res/xml/main_preferences.xml | 6 ++- 10 files changed, 98 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/io/heckel/ntfy/data/Repository.kt b/app/src/main/java/io/heckel/ntfy/data/Repository.kt index 9d69112..89f955c 100644 --- a/app/src/main/java/io/heckel/ntfy/data/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/data/Repository.kt @@ -162,14 +162,18 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri return sharedPrefs.getInt(SHARED_PREFS_MIN_PRIORITY, 1) // 1/low means all priorities } - fun getAutoDownloadEnabled(): Boolean { - val defaultEnabled = Build.VERSION.SDK_INT > Build.VERSION_CODES.P // Need to request permission on older versions - return sharedPrefs.getBoolean(SHARED_PREFS_AUTO_DOWNLOAD_ENABLED, defaultEnabled) + fun getAutoDownloadMaxSize(): Long { + val defaultValue = if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { + AUTO_DOWNLOAD_NEVER // Need to request permission on older versions + } else { + AUTO_DOWNLOAD_DEFAULT + } + return sharedPrefs.getLong(SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE, defaultValue) } - fun setAutoDownloadEnabled(enabled: Boolean) { + fun setAutoDownloadMaxSize(maxSize: Long) { sharedPrefs.edit() - .putBoolean(SHARED_PREFS_AUTO_DOWNLOAD_ENABLED, enabled) + .putLong(SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE, maxSize) .apply() } @@ -303,12 +307,14 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil" const val SHARED_PREFS_MIN_PRIORITY = "MinPriority" - const val SHARED_PREFS_AUTO_DOWNLOAD_ENABLED = "AutoDownload" + const val SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE = "AutoDownload" const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled" const val SHARED_PREFS_UNIFIED_PUSH_ENABLED = "UnifiedPushEnabled" const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL" - const val PREVIEWS_CACHE_DIR = "Previews" + const val AUTO_DOWNLOAD_NEVER = 0L + const val AUTO_DOWNLOAD_ALWAYS = 1L + const val AUTO_DOWNLOAD_DEFAULT = 1024 * 1024L // Must match a value in values.xml private const val TAG = "NtfyRepository" private var instance: Repository? = null diff --git a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt index c5b301f..a39069a 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/ApiService.kt @@ -7,6 +7,7 @@ import com.google.gson.Gson import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.data.Attachment import io.heckel.ntfy.data.Notification +import io.heckel.ntfy.data.PROGRESS_NONE import io.heckel.ntfy.util.* import okhttp3.* import okhttp3.RequestBody.Companion.toRequestBody @@ -54,11 +55,7 @@ class ApiService { } } - fun poll(subscriptionId: Long, baseUrl: String, topic: String): List { - return poll(subscriptionId, baseUrl, topic, 0) - } - - fun poll(subscriptionId: Long, baseUrl: String, topic: String, since: Long): List { + fun poll(subscriptionId: Long, baseUrl: String, topic: String, since: Long = 0L): List { val sinceVal = if (since == 0L) "all" else since.toString() val url = topicUrlJsonPoll(baseUrl, topic, sinceVal) Log.d(TAG, "Polling topic $url") diff --git a/app/src/main/java/io/heckel/ntfy/msg/DownloadWorker.kt b/app/src/main/java/io/heckel/ntfy/msg/DownloadWorker.kt index 60f2e28..f48feff 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/DownloadWorker.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/DownloadWorker.kt @@ -119,6 +119,7 @@ class DownloadWorker(private val context: Context, params: WorkerParameters) : W } catch (e: Exception) { Log.w(TAG, "Attachment download failed", e) + // Mark attachment download as failed val newAttachment = attachment.copy(progress = PROGRESS_FAILED) val newNotification = notification.copy(attachment = newAttachment) notifier.update(subscription, newNotification) diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt index 594a129..8ce3875 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationDispatcher.kt @@ -2,9 +2,6 @@ package io.heckel.ntfy.msg import android.content.Context import android.util.Log -import androidx.work.OneTimeWorkRequest -import androidx.work.WorkManager -import androidx.work.workDataOf import io.heckel.ntfy.data.Notification import io.heckel.ntfy.data.Repository import io.heckel.ntfy.data.Subscription @@ -31,7 +28,7 @@ class NotificationDispatcher(val context: Context, val repository: Repository) { val notify = shouldNotify(subscription, notification, muted) val broadcast = shouldBroadcast(subscription) val distribute = shouldDistribute(subscription) - val download = shouldDownload(subscription, notification) + val download = shouldDownload(notification) if (notify) { notifier.display(subscription, notification) } @@ -48,8 +45,21 @@ class NotificationDispatcher(val context: Context, val repository: Repository) { } } - private fun shouldDownload(subscription: Subscription, notification: Notification): Boolean { - return notification.attachment != null && repository.getAutoDownloadEnabled() + private fun shouldDownload(notification: Notification): Boolean { + if (notification.attachment == null) { + return false + } + val maxAutoDownloadSize = repository.getAutoDownloadMaxSize() + when (maxAutoDownloadSize) { + Repository.AUTO_DOWNLOAD_ALWAYS -> return true + Repository.AUTO_DOWNLOAD_NEVER -> return false + else -> { + if (notification.attachment.size == null) { + return false + } + return notification.attachment.size <= maxAutoDownloadSize + } + } } private fun shouldNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean { diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt index dea593c..54ff6b2 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -153,8 +153,10 @@ class SubscriberService : Service() { // Start new connections and restart connections (if subscriptions have changed) instantSubscriptionsByBaseUrl.forEach { (baseUrl, subscriptions) -> + // Do NOT request old messages for new connections; we'll call poll() in MainActivity. + // This is important, so we don't download attachments from old messages, which is not desired. + var since = System.currentTimeMillis()/1000 val connection = connections[baseUrl] - var since = 0L if (connection != null && !connection.matches(subscriptions.values)) { since = connection.since() connections.remove(baseUrl) diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index 9c6d430..559aa51 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -20,6 +20,7 @@ import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R import io.heckel.ntfy.app.Application import io.heckel.ntfy.data.Repository +import io.heckel.ntfy.util.formatBytes import io.heckel.ntfy.util.formatDateShort import io.heckel.ntfy.util.toPriorityString @@ -49,6 +50,8 @@ class SettingsActivity : AppCompatActivity() { } class SettingsFragment(val repository: Repository, private val supportFragmentManager: FragmentManager) : PreferenceFragmentCompat() { + private var autoDownloadSelection = repository.getAutoDownloadMaxSize() // Only used for <= Android P, due to permissions request + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.main_preferences, rootKey) @@ -115,27 +118,30 @@ class SettingsActivity : AppCompatActivity() { // Auto download val autoDownloadPrefId = context?.getString(R.string.settings_notifications_auto_download_key) ?: return - val autoDownload: SwitchPreference? = findPreference(autoDownloadPrefId) - autoDownload?.isChecked = repository.getAutoDownloadEnabled() + val autoDownload: ListPreference? = findPreference(autoDownloadPrefId) + autoDownload?.value = repository.getAutoDownloadMaxSize().toString() autoDownload?.preferenceDataStore = object : PreferenceDataStore() { - override fun putBoolean(key: String?, value: Boolean) { - repository.setAutoDownloadEnabled(value) + override fun putString(key: String?, value: String?) { + val maxSize = value?.toLongOrNull() ?:return + repository.setAutoDownloadMaxSize(maxSize) } - override fun getBoolean(key: String?, defValue: Boolean): Boolean { - return repository.getAutoDownloadEnabled() + override fun getString(key: String?, defValue: String?): String { + return repository.getAutoDownloadMaxSize().toString() } } - autoDownload?.summaryProvider = Preference.SummaryProvider { pref -> - if (pref.isChecked) { - getString(R.string.settings_notifications_auto_download_summary_on) - } else { - getString(R.string.settings_notifications_auto_download_summary_off) + autoDownload?.summaryProvider = Preference.SummaryProvider { pref -> + val maxSize = pref.value.toLongOrNull() ?: repository.getAutoDownloadMaxSize() + when (maxSize) { + Repository.AUTO_DOWNLOAD_NEVER -> getString(R.string.settings_notifications_auto_download_summary_never) + Repository.AUTO_DOWNLOAD_ALWAYS -> getString(R.string.settings_notifications_auto_download_summary_always) + else -> getString(R.string.settings_notifications_auto_download_summary_smaller_than_x, formatBytes(maxSize, decimals = 0)) } } if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) { autoDownload?.setOnPreferenceChangeListener { _, v -> if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), REQUEST_CODE_WRITE_EXTERNAL_STORAGE_PERMISSION_FOR_AUTO_DOWNLOAD) + autoDownloadSelection = v.toString().toLongOrNull() ?: repository.getAutoDownloadMaxSize() false // If permission is granted, auto-download will be enabled in onRequestPermissionsResult() } else { true @@ -222,10 +228,11 @@ class SettingsActivity : AppCompatActivity() { } } - fun enableAutoDownload() { + fun setAutoDownload() { val autoDownloadPrefId = context?.getString(R.string.settings_notifications_auto_download_key) ?: return - val autoDownload: SwitchPreference? = findPreference(autoDownloadPrefId) - autoDownload?.isChecked = true + val autoDownload: ListPreference? = findPreference(autoDownloadPrefId) + autoDownload?.value = autoDownloadSelection.toString() + repository.setAutoDownloadMaxSize(autoDownloadSelection) } } @@ -233,15 +240,14 @@ class SettingsActivity : AppCompatActivity() { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == REQUEST_CODE_WRITE_EXTERNAL_STORAGE_PERMISSION_FOR_AUTO_DOWNLOAD) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - enableAutoDownload() - repository.setAutoDownloadEnabled(true) + setAutoDownload() } } } - private fun enableAutoDownload() { + private fun setAutoDownload() { if (!this::fragment.isInitialized) return - fragment.enableAutoDownload() + fragment.setAutoDownload() } companion object { diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index 20ce1d9..82b4586 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -13,6 +13,7 @@ import java.security.SecureRandom import java.text.DateFormat import java.text.StringCharacterIterator import java.util.* +import kotlin.math.abs fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}" fun topicUrlUp(baseUrl: String, topic: String) = "${baseUrl}/${topic}?up=1" // UnifiedPush @@ -163,8 +164,8 @@ inline fun safeLet(p1: T1?, p2: T2?, block: (T1, T2)- return if (p1 != null && p2 != null) block(p1, p2) else null } -fun formatBytes(bytes: Long): String { - val absB = if (bytes == Long.MIN_VALUE) Long.MAX_VALUE else Math.abs(bytes) +fun formatBytes(bytes: Long, decimals: Int = 1): String { + val absB = if (bytes == Long.MIN_VALUE) Long.MAX_VALUE else abs(bytes) if (absB < 1024) { return "$bytes B" } @@ -177,7 +178,7 @@ fun formatBytes(bytes: Long): String { i -= 10 } value *= java.lang.Long.signum(bytes).toLong() - return java.lang.String.format("%.1f %cB", value / 1024.0, ci.current()) + return java.lang.String.format("%.${decimals}f %cB", value / 1024.0, ci.current()) } fun supportedImage(mimeType: String?): Boolean { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c85925..1d84500 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -193,8 +193,17 @@ Only max priority AutoDownload Auto download attachments - Attachments are automatically downloaded - Attachments are not automatically downloaded + Attachments are always downloaded automatically + Attachments are never downloaded automatically + Attachments up to %1$s are downloaded automatically + Never download automatically + Always download automatically + If smaller than 100 KB + If smaller than 500 KB + If smaller than 1 MB + If smaller than 5 MB + If smaller than 10 MB + If smaller than 50 MB UnifiedPush Allows other apps to use ntfy as a message distributor. Find out more at unifiedpush.org. UnifiedPushEnabled diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 4dfb50c..f6f8591 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -14,4 +14,24 @@ 4 5 + + @string/settings_notifications_auto_download_never + @string/settings_notifications_auto_download_always + @string/settings_notifications_auto_download_100k + @string/settings_notifications_auto_download_500k + @string/settings_notifications_auto_download_1m + @string/settings_notifications_auto_download_5m + @string/settings_notifications_auto_download_10m + @string/settings_notifications_auto_download_50m + + + 0 + 1 + 102400 + 512000 + 1048576 + 5242880 + 10485760 + 52428800 + diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml index 49561d2..bc0a169 100644 --- a/app/src/main/res/xml/main_preferences.xml +++ b/app/src/main/res/xml/main_preferences.xml @@ -13,10 +13,12 @@ app:entries="@array/settings_notifications_min_priority_entries" app:entryValues="@array/settings_notifications_min_priority_values" app:defaultValue="1"/> - + app:entries="@array/settings_notifications_auto_download_entries" + app:entryValues="@array/settings_notifications_auto_download_values" + app:defaultValue="1"/>