Loading app/src/main/java/io/heckel/ntfy/db/Repository.kt +3 −3 Original line number Diff line number Diff line Loading @@ -422,9 +422,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas private const val TAG = "NtfyRepository" private var instance: Repository? = null fun getInstance(activity: Activity): Repository { val database = Database.getInstance(activity.applicationContext) val sharedPrefs = activity.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) fun getInstance(context: Context): Repository { val database = Database.getInstance(context.applicationContext) val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) return getInstance(sharedPrefs, database) } Loading app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +21 −35 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ import java.nio.charset.StandardCharsets.UTF_8 import java.util.concurrent.TimeUnit import kotlin.random.Random class ApiService { private val client = OkHttpClient.Builder() .callTimeout(15, TimeUnit.SECONDS) // Total timeout for entire request Loading @@ -29,27 +28,23 @@ class ApiService { .build() private val parser = NotificationParser() fun publish(baseUrl: String, topic: String, message: String, title: String, priority: Int, tags: List<String>, delay: String) { fun publish(baseUrl: String, topic: String, user: User?, message: String, title: String, priority: Int, tags: List<String>, delay: String) { val url = topicUrl(baseUrl, topic) Log.d(TAG, "Publishing to $url") // XXXXXXXXXXXx var builder = Request.Builder() .url(url) val builder = builder(url, user) .put(message.toRequestBody()) .addHeader("User-Agent", USER_AGENT) if (priority in 1..5) { builder = builder.addHeader("X-Priority", priority.toString()) builder.addHeader("X-Priority", priority.toString()) } if (tags.isNotEmpty()) { builder = builder.addHeader("X-Tags", tags.joinToString(",")) builder.addHeader("X-Tags", tags.joinToString(",")) } if (title.isNotEmpty()) { builder = builder.addHeader("X-Title", title) builder.addHeader("X-Title", title) } if (delay.isNotEmpty()) { builder = builder.addHeader("X-Delay", delay) builder.addHeader("X-Delay", delay) } client.newCall(builder.build()).execute().use { response -> if (!response.isSuccessful) { Loading @@ -59,18 +54,12 @@ class ApiService { } } fun poll(subscriptionId: Long, baseUrl: String, topic: String, since: Long = 0L): List<Notification> { fun poll(subscriptionId: Long, baseUrl: String, topic: String, user: User?, since: Long = 0L): List<Notification> { val sinceVal = if (since == 0L) "all" else since.toString() val url = topicUrlJsonPoll(baseUrl, topic, sinceVal) Log.d(TAG, "Polling topic $url") val request = Request.Builder() .url(url) .addHeader("User-Agent", USER_AGENT) .build() // XXXXXXXXXXXx val request = builder(url, user).build() client.newCall(request).execute().use { response -> if (!response.isSuccessful) { throw Exception("Unexpected response ${response.code} when polling topic $url") Loading @@ -97,14 +86,7 @@ class ApiService { val sinceVal = if (since == 0L) "all" else since.toString() val url = topicUrlJson(baseUrl, topics, sinceVal) Log.d(TAG, "Opening subscription connection to $url") val builder = Request.Builder() .get() .url(url) .addHeader("User-Agent", USER_AGENT) if (user != null) { builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8)) } val request = builder.build() val request = builder(url, user).build() val call = subscriberClient.newCall(request) call.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { Loading Loading @@ -140,14 +122,7 @@ class ApiService { Log.d(TAG, "Checking read access for user ${user.username} against ${topicUrl(baseUrl, topic)}") } val url = topicUrlAuth(baseUrl, topic) val builder = Request.Builder() .get() .url(url) .addHeader("User-Agent", USER_AGENT) if (user != null) { builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8)) } val request = builder.build() val request = builder(url, user).build() client.newCall(request).execute().use { response -> return if (user == null) { response.isSuccessful || response.code == 404 // Treat 404 as success (old server; to be removed in future versions) Loading @@ -157,6 +132,16 @@ class ApiService { } } private fun builder(url: String, user: User?): Request.Builder { val builder = Request.Builder() .url(url) .addHeader("User-Agent", USER_AGENT) if (user != null) { builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8)) } return builder } companion object { val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})" private const val TAG = "NtfyApiService" Loading @@ -165,5 +150,6 @@ class ApiService { const val CONTROL_TOPIC = "~control" const val EVENT_MESSAGE = "message" const val EVENT_KEEPALIVE = "keepalive" const val EVENT_POLL_REQUEST = "poll_request" } } app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt +8 −2 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import io.heckel.ntfy.R import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.joinTagsMap Loading Loading @@ -65,9 +66,12 @@ class BroadcastService(private val ctx: Context) { } val delay = getStringExtra(intent,"delay") ?: "" GlobalScope.launch(Dispatchers.IO) { val repository = Repository.getInstance(ctx) val user = repository.getUser(baseUrl) // May be null api.publish( baseUrl = baseUrl, topic = topic, user = user, message = message, title = title, priority = priority, Loading @@ -94,8 +98,10 @@ class BroadcastService(private val ctx: Context) { companion object { private const val TAG = "NtfyBroadcastService" private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED" private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE" // If changed, change in manifest too! private const val DOES_NOT_EXIST = -2586000 // These constants cannot be changed without breaking the contract; also see manifest private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED" private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE" } } app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +4 −2 Original line number Diff line number Diff line Loading @@ -269,6 +269,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra lifecycleScope.launch(Dispatchers.IO) { try { val user = repository.getUser(subscriptionBaseUrl) // May be null val possibleTags = listOf( "warning", "skull", "success", "triangular_flag_on_post", "de", "dog", "rotating_light", "cat", "bike", // Emojis "backup", "rsync", "de-server1", "this-is-a-tag" Loading @@ -277,7 +278,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra val tags = possibleTags.shuffled().take(Random.nextInt(0, 4)) val title = if (Random.nextBoolean()) getString(R.string.detail_test_title) else "" val message = getString(R.string.detail_test_message, priority) api.publish(subscriptionBaseUrl, subscriptionTopic, message, title, priority, tags, delay = "") api.publish(subscriptionBaseUrl, subscriptionTopic, user, message, title, priority, tags, delay = "") } catch (e: Exception) { runOnUiThread { Toast Loading Loading @@ -339,7 +340,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra lifecycleScope.launch(Dispatchers.IO) { try { val notifications = api.poll(subscriptionId, subscriptionBaseUrl, subscriptionTopic) val user = repository.getUser(subscriptionBaseUrl) // May be null val notifications = api.poll(subscriptionId, subscriptionBaseUrl, subscriptionTopic, user) val newNotifications = repository.onlyNewNotifications(subscriptionId, notifications) val toastMessage = if (newNotifications.isEmpty()) { getString(R.string.refresh_message_no_results) Loading app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +6 −4 Original line number Diff line number Diff line Loading @@ -193,10 +193,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc val work = PeriodicWorkRequestBuilder<PollWorker>(POLL_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES) .setConstraints(constraints) .addTag(PollWorker.TAG) .addTag(PollWorker.WORK_NAME_PERIODIC) .addTag(PollWorker.WORK_NAME_PERIODIC_ALL) .build() Log.d(TAG, "Poll worker: Scheduling period work every $POLL_WORKER_INTERVAL_MINUTES minutes") workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC, workPolicy, work) workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC_ALL, workPolicy, work) } private fun startPeriodicServiceRestartWorker() { Loading Loading @@ -375,7 +375,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc // Fetch cached messages lifecycleScope.launch(Dispatchers.IO) { try { val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic) val user = repository.getUser(subscription.baseUrl) // May be null val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user) notifications.forEach { notification -> repository.addNotification(notification) } } catch (e: Exception) { Log.e(TAG, "Unable to fetch notifications: ${e.stackTrace}") Loading Loading @@ -418,7 +419,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc var newNotificationsCount = 0 repository.getSubscriptions().forEach { subscription -> try { val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic) val user = repository.getUser(subscription.baseUrl) // May be null val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user) val newNotifications = repository.onlyNewNotifications(subscription.id, notifications) newNotifications.forEach { notification -> newNotificationsCount++ Loading Loading
app/src/main/java/io/heckel/ntfy/db/Repository.kt +3 −3 Original line number Diff line number Diff line Loading @@ -422,9 +422,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas private const val TAG = "NtfyRepository" private var instance: Repository? = null fun getInstance(activity: Activity): Repository { val database = Database.getInstance(activity.applicationContext) val sharedPrefs = activity.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) fun getInstance(context: Context): Repository { val database = Database.getInstance(context.applicationContext) val sharedPrefs = context.getSharedPreferences(SHARED_PREFS_ID, Context.MODE_PRIVATE) return getInstance(sharedPrefs, database) } Loading
app/src/main/java/io/heckel/ntfy/msg/ApiService.kt +21 −35 Original line number Diff line number Diff line Loading @@ -16,7 +16,6 @@ import java.nio.charset.StandardCharsets.UTF_8 import java.util.concurrent.TimeUnit import kotlin.random.Random class ApiService { private val client = OkHttpClient.Builder() .callTimeout(15, TimeUnit.SECONDS) // Total timeout for entire request Loading @@ -29,27 +28,23 @@ class ApiService { .build() private val parser = NotificationParser() fun publish(baseUrl: String, topic: String, message: String, title: String, priority: Int, tags: List<String>, delay: String) { fun publish(baseUrl: String, topic: String, user: User?, message: String, title: String, priority: Int, tags: List<String>, delay: String) { val url = topicUrl(baseUrl, topic) Log.d(TAG, "Publishing to $url") // XXXXXXXXXXXx var builder = Request.Builder() .url(url) val builder = builder(url, user) .put(message.toRequestBody()) .addHeader("User-Agent", USER_AGENT) if (priority in 1..5) { builder = builder.addHeader("X-Priority", priority.toString()) builder.addHeader("X-Priority", priority.toString()) } if (tags.isNotEmpty()) { builder = builder.addHeader("X-Tags", tags.joinToString(",")) builder.addHeader("X-Tags", tags.joinToString(",")) } if (title.isNotEmpty()) { builder = builder.addHeader("X-Title", title) builder.addHeader("X-Title", title) } if (delay.isNotEmpty()) { builder = builder.addHeader("X-Delay", delay) builder.addHeader("X-Delay", delay) } client.newCall(builder.build()).execute().use { response -> if (!response.isSuccessful) { Loading @@ -59,18 +54,12 @@ class ApiService { } } fun poll(subscriptionId: Long, baseUrl: String, topic: String, since: Long = 0L): List<Notification> { fun poll(subscriptionId: Long, baseUrl: String, topic: String, user: User?, since: Long = 0L): List<Notification> { val sinceVal = if (since == 0L) "all" else since.toString() val url = topicUrlJsonPoll(baseUrl, topic, sinceVal) Log.d(TAG, "Polling topic $url") val request = Request.Builder() .url(url) .addHeader("User-Agent", USER_AGENT) .build() // XXXXXXXXXXXx val request = builder(url, user).build() client.newCall(request).execute().use { response -> if (!response.isSuccessful) { throw Exception("Unexpected response ${response.code} when polling topic $url") Loading @@ -97,14 +86,7 @@ class ApiService { val sinceVal = if (since == 0L) "all" else since.toString() val url = topicUrlJson(baseUrl, topics, sinceVal) Log.d(TAG, "Opening subscription connection to $url") val builder = Request.Builder() .get() .url(url) .addHeader("User-Agent", USER_AGENT) if (user != null) { builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8)) } val request = builder.build() val request = builder(url, user).build() val call = subscriberClient.newCall(request) call.enqueue(object : Callback { override fun onResponse(call: Call, response: Response) { Loading Loading @@ -140,14 +122,7 @@ class ApiService { Log.d(TAG, "Checking read access for user ${user.username} against ${topicUrl(baseUrl, topic)}") } val url = topicUrlAuth(baseUrl, topic) val builder = Request.Builder() .get() .url(url) .addHeader("User-Agent", USER_AGENT) if (user != null) { builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8)) } val request = builder.build() val request = builder(url, user).build() client.newCall(request).execute().use { response -> return if (user == null) { response.isSuccessful || response.code == 404 // Treat 404 as success (old server; to be removed in future versions) Loading @@ -157,6 +132,16 @@ class ApiService { } } private fun builder(url: String, user: User?): Request.Builder { val builder = Request.Builder() .url(url) .addHeader("User-Agent", USER_AGENT) if (user != null) { builder.addHeader("Authorization", Credentials.basic(user.username, user.password, UTF_8)) } return builder } companion object { val USER_AGENT = "ntfy/${BuildConfig.VERSION_NAME} (${BuildConfig.FLAVOR}; Android ${Build.VERSION.RELEASE}; SDK ${Build.VERSION.SDK_INT})" private const val TAG = "NtfyApiService" Loading @@ -165,5 +150,6 @@ class ApiService { const val CONTROL_TOPIC = "~control" const val EVENT_MESSAGE = "message" const val EVENT_KEEPALIVE = "keepalive" const val EVENT_POLL_REQUEST = "poll_request" } }
app/src/main/java/io/heckel/ntfy/msg/BroadcastService.kt +8 −2 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import io.heckel.ntfy.R import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.joinTagsMap Loading Loading @@ -65,9 +66,12 @@ class BroadcastService(private val ctx: Context) { } val delay = getStringExtra(intent,"delay") ?: "" GlobalScope.launch(Dispatchers.IO) { val repository = Repository.getInstance(ctx) val user = repository.getUser(baseUrl) // May be null api.publish( baseUrl = baseUrl, topic = topic, user = user, message = message, title = title, priority = priority, Loading @@ -94,8 +98,10 @@ class BroadcastService(private val ctx: Context) { companion object { private const val TAG = "NtfyBroadcastService" private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED" private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE" // If changed, change in manifest too! private const val DOES_NOT_EXIST = -2586000 // These constants cannot be changed without breaking the contract; also see manifest private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED" private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE" } }
app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +4 −2 Original line number Diff line number Diff line Loading @@ -269,6 +269,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra lifecycleScope.launch(Dispatchers.IO) { try { val user = repository.getUser(subscriptionBaseUrl) // May be null val possibleTags = listOf( "warning", "skull", "success", "triangular_flag_on_post", "de", "dog", "rotating_light", "cat", "bike", // Emojis "backup", "rsync", "de-server1", "this-is-a-tag" Loading @@ -277,7 +278,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra val tags = possibleTags.shuffled().take(Random.nextInt(0, 4)) val title = if (Random.nextBoolean()) getString(R.string.detail_test_title) else "" val message = getString(R.string.detail_test_message, priority) api.publish(subscriptionBaseUrl, subscriptionTopic, message, title, priority, tags, delay = "") api.publish(subscriptionBaseUrl, subscriptionTopic, user, message, title, priority, tags, delay = "") } catch (e: Exception) { runOnUiThread { Toast Loading Loading @@ -339,7 +340,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra lifecycleScope.launch(Dispatchers.IO) { try { val notifications = api.poll(subscriptionId, subscriptionBaseUrl, subscriptionTopic) val user = repository.getUser(subscriptionBaseUrl) // May be null val notifications = api.poll(subscriptionId, subscriptionBaseUrl, subscriptionTopic, user) val newNotifications = repository.onlyNewNotifications(subscriptionId, notifications) val toastMessage = if (newNotifications.isEmpty()) { getString(R.string.refresh_message_no_results) Loading
app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +6 −4 Original line number Diff line number Diff line Loading @@ -193,10 +193,10 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc val work = PeriodicWorkRequestBuilder<PollWorker>(POLL_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES) .setConstraints(constraints) .addTag(PollWorker.TAG) .addTag(PollWorker.WORK_NAME_PERIODIC) .addTag(PollWorker.WORK_NAME_PERIODIC_ALL) .build() Log.d(TAG, "Poll worker: Scheduling period work every $POLL_WORKER_INTERVAL_MINUTES minutes") workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC, workPolicy, work) workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC_ALL, workPolicy, work) } private fun startPeriodicServiceRestartWorker() { Loading Loading @@ -375,7 +375,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc // Fetch cached messages lifecycleScope.launch(Dispatchers.IO) { try { val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic) val user = repository.getUser(subscription.baseUrl) // May be null val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user) notifications.forEach { notification -> repository.addNotification(notification) } } catch (e: Exception) { Log.e(TAG, "Unable to fetch notifications: ${e.stackTrace}") Loading Loading @@ -418,7 +419,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc var newNotificationsCount = 0 repository.getSubscriptions().forEach { subscription -> try { val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic) val user = repository.getUser(subscription.baseUrl) // May be null val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic, user) val newNotifications = repository.onlyNewNotifications(subscription.id, notifications) newNotifications.forEach { notification -> newNotificationsCount++ Loading