✨ Add batch service for migrating existing messages
This commit is contained in:
parent
78f9621afa
commit
412180f443
@ -198,6 +198,10 @@
|
|||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name=".service.BatchProcessorService"
|
||||||
|
android:exported="false" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".StatusActivity"
|
android:name=".StatusActivity"
|
||||||
android:label="@string/title_activity_status"
|
android:label="@string/title_activity_status"
|
||||||
|
@ -17,15 +17,17 @@
|
|||||||
package ch.dissem.apps.abit
|
package ch.dissem.apps.abit
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.*
|
||||||
import android.content.Intent
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
import android.preference.PreferenceManager
|
import android.preference.PreferenceManager
|
||||||
import android.support.v4.content.FileProvider.getUriForFile
|
import android.support.v4.content.FileProvider.getUriForFile
|
||||||
import android.support.v7.preference.Preference
|
import android.support.v7.preference.Preference
|
||||||
import android.support.v7.preference.PreferenceFragmentCompat
|
import android.support.v7.preference.PreferenceFragmentCompat
|
||||||
|
import android.support.v7.preference.SwitchPreferenceCompat
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import ch.dissem.apps.abit.service.BatchProcessorService
|
||||||
|
import ch.dissem.apps.abit.service.SimpleJob
|
||||||
import ch.dissem.apps.abit.service.Singleton
|
import ch.dissem.apps.abit.service.Singleton
|
||||||
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
import ch.dissem.apps.abit.synchronization.SyncAdapter
|
||||||
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW
|
import ch.dissem.apps.abit.util.Constants.PREFERENCE_SERVER_POW
|
||||||
@ -55,9 +57,12 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
findPreference("export")?.onPreferenceClickListener = exportClickListener()
|
findPreference("export")?.onPreferenceClickListener = exportClickListener()
|
||||||
findPreference("import")?.onPreferenceClickListener = importClickListener()
|
findPreference("import")?.onPreferenceClickListener = importClickListener()
|
||||||
findPreference("status").onPreferenceClickListener = statusClickListener()
|
findPreference("status").onPreferenceClickListener = statusClickListener()
|
||||||
val conversationInit = findPreference("emulate_conversations_initialize")
|
val conversationInit = findPreference("emulate_conversations_initialize") as? SwitchPreferenceCompat
|
||||||
conversationInit?.onPreferenceClickListener = conversationInitClickListener(conversationInit)
|
conversationInit?.onPreferenceClickListener = conversationInitClickListener()
|
||||||
findPreference("emulate_conversations")?.onPreferenceChangeListener = emulateConversationChangeListener(conversationInit)
|
findPreference("emulate_conversations")?.apply {
|
||||||
|
onPreferenceChangeListener = emulateConversationChangeListener(conversationInit)
|
||||||
|
isEnabled = conversationInit?.isChecked ?: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aboutClickListener() = Preference.OnPreferenceClickListener {
|
private fun aboutClickListener() = Preference.OnPreferenceClickListener {
|
||||||
@ -199,45 +204,42 @@ class SettingsFragment : PreferenceFragmentCompat(), SharedPreferences.OnSharedP
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun conversationInitClickListener(conversationInit: Preference) = Preference.OnPreferenceClickListener {
|
private val connection = object : ServiceConnection {
|
||||||
val ctx = activity?.applicationContext
|
override fun onServiceConnected(name: ComponentName, service: IBinder) {
|
||||||
?: throw IllegalStateException("Context not available")
|
if (service is BatchProcessorService.BatchBinder) {
|
||||||
conversationInit.isEnabled = false
|
val messageRepo = Singleton.getMessageRepository(service.service)
|
||||||
Toast.makeText(ctx, R.string.emulate_conversations_summary, Toast.LENGTH_SHORT).show()
|
val conversationService = Singleton.getConversationService(service.service)
|
||||||
|
|
||||||
doAsync {
|
service.process(SimpleJob<Plaintext>(
|
||||||
val messageRepo = Singleton.getMessageRepository(ctx)
|
messageRepo.count(),
|
||||||
val conversationService = Singleton.getConversationService(ctx)
|
{ messageRepo.findNextLegacyMessages(it) },
|
||||||
do {
|
{ msg ->
|
||||||
var previous: Plaintext? = null
|
if (msg.encoding == Plaintext.Encoding.SIMPLE) {
|
||||||
val messages = messageRepo.findNextLegacyMessages(previous)
|
conversationService.getSubject(listOf(msg))?.let { subject ->
|
||||||
messages.forEach { msg ->
|
msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())
|
||||||
if (msg.encoding == Plaintext.Encoding.SIMPLE) {
|
messageRepo.save(msg)
|
||||||
conversationService.getSubject(listOf(msg))?.let { subject ->
|
Thread.yield()
|
||||||
msg.conversationId = UUID.nameUUIDFromBytes(subject.toByteArray())
|
}
|
||||||
messageRepo.save(msg)
|
|
||||||
Thread.yield()
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
R.drawable.ic_notification_batch,
|
||||||
if (!messages.isEmpty()) {
|
R.string.emulate_conversations_batch
|
||||||
previous = messages.last()
|
))
|
||||||
}
|
|
||||||
} while (!messages.isEmpty())
|
|
||||||
|
|
||||||
uiThread {
|
|
||||||
Toast.makeText(
|
|
||||||
ctx,
|
|
||||||
R.string.cleanup_notification_end,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
conversationInit.isEnabled = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return@OnPreferenceClickListener true
|
|
||||||
|
override fun onServiceDisconnected(name: ComponentName) {
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { preference, newValue ->
|
private fun conversationInitClickListener() = Preference.OnPreferenceClickListener {
|
||||||
|
val ctx = activity?.applicationContext
|
||||||
|
?: throw IllegalStateException("Context not available")
|
||||||
|
ctx.bindService(Intent(ctx, BatchProcessorService::class.java), connection, Context.BIND_AUTO_CREATE)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun emulateConversationChangeListener(conversationInit: Preference?) = Preference.OnPreferenceChangeListener { _, newValue ->
|
||||||
conversationInit?.isEnabled = newValue as Boolean
|
conversationInit?.isEnabled = newValue as Boolean
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2016 Christian Basler
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package ch.dissem.apps.abit.notification
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.v4.app.NotificationCompat
|
||||||
|
import ch.dissem.apps.abit.R
|
||||||
|
import ch.dissem.apps.abit.service.Job
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ongoing notification while proof of work is in progress.
|
||||||
|
*/
|
||||||
|
class BatchNotification(ctx: Context) : AbstractNotification(ctx) {
|
||||||
|
|
||||||
|
private val builder = NotificationCompat.Builder(ctx, ONGOING_CHANNEL_ID)
|
||||||
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
|
.setUsesChronometer(true)
|
||||||
|
|
||||||
|
init {
|
||||||
|
initChannel(ONGOING_CHANNEL_ID, R.color.colorAccent)
|
||||||
|
notification = builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override val notificationId = ONGOING_NOTIFICATION_ID
|
||||||
|
|
||||||
|
fun update(job: Job): BatchNotification {
|
||||||
|
|
||||||
|
builder.setContentTitle(ctx.getString(job.description))
|
||||||
|
.setSmallIcon(job.icon)
|
||||||
|
.setProgress(job.numberOfItems, job.numberOfProcessedItems, job.numberOfItems <= 0)
|
||||||
|
|
||||||
|
notification = builder.build()
|
||||||
|
show()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val ONGOING_NOTIFICATION_ID = 4
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,13 @@ class AndroidMessageRepository(private val sql: SqlHelper) : AbstractMessageRepo
|
|||||||
super.findMessages(label, offset, limit)
|
super.findMessages(label, offset, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun count() = DatabaseUtils.queryNumEntries(
|
||||||
|
sql.readableDatabase,
|
||||||
|
TABLE_NAME,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
).toInt()
|
||||||
|
|
||||||
override fun countUnread(label: Label?) = when {
|
override fun countUnread(label: Label?) = when {
|
||||||
label === LABEL_ARCHIVE -> 0
|
label === LABEL_ARCHIVE -> 0
|
||||||
label == null -> DatabaseUtils.queryNumEntries(
|
label == null -> DatabaseUtils.queryNumEntries(
|
||||||
|
@ -0,0 +1,117 @@
|
|||||||
|
package ch.dissem.apps.abit.service
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Binder
|
||||||
|
import android.support.annotation.DrawableRes
|
||||||
|
import android.support.annotation.StringRes
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
|
import ch.dissem.apps.abit.notification.BatchNotification
|
||||||
|
import ch.dissem.apps.abit.notification.BatchNotification.Companion.ONGOING_NOTIFICATION_ID
|
||||||
|
import org.jetbrains.anko.doAsync
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class BatchProcessorService : Service() {
|
||||||
|
private lateinit var notification: BatchNotification
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
notification = BatchNotification(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(intent: Intent) = BatchBinder(this)
|
||||||
|
|
||||||
|
class BatchBinder internal constructor(val service: BatchProcessorService) : Binder() {
|
||||||
|
private val notification = service.notification
|
||||||
|
|
||||||
|
fun process(job: Job) = synchronized(queue) {
|
||||||
|
ContextCompat.startForegroundService(
|
||||||
|
service,
|
||||||
|
Intent(service, BatchProcessorService::class.java)
|
||||||
|
)
|
||||||
|
service.startForeground(
|
||||||
|
ONGOING_NOTIFICATION_ID,
|
||||||
|
notification.notification
|
||||||
|
)
|
||||||
|
if (!working) {
|
||||||
|
working = true
|
||||||
|
service.processQueue(job)
|
||||||
|
} else {
|
||||||
|
queue.add(job)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processQueue(job: Job) {
|
||||||
|
doAsync {
|
||||||
|
var next: Job? = job
|
||||||
|
while (next != null) {
|
||||||
|
next.process(notification)
|
||||||
|
|
||||||
|
synchronized(queue) {
|
||||||
|
next = queue.poll()
|
||||||
|
if (next == null) {
|
||||||
|
working = false
|
||||||
|
stopForeground(true)
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private var working = false
|
||||||
|
private val queue = LinkedList<Job>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Job {
|
||||||
|
val icon: Int
|
||||||
|
@DrawableRes get
|
||||||
|
|
||||||
|
val description: Int
|
||||||
|
@StringRes get
|
||||||
|
|
||||||
|
val numberOfItems: Int
|
||||||
|
var numberOfProcessedItems: Int
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the job. This shouldn't happen in a separate thread, as this is handled by the service.
|
||||||
|
*/
|
||||||
|
fun process(notification: BatchNotification)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SimpleJob<T>(
|
||||||
|
override val numberOfItems: Int,
|
||||||
|
/**
|
||||||
|
* Provides the next batch of items, given the last item of the previous batch,
|
||||||
|
* or null for the first batch.
|
||||||
|
*/
|
||||||
|
private val provider: (T?) -> List<T>,
|
||||||
|
/**
|
||||||
|
* Processes an item.
|
||||||
|
*/
|
||||||
|
private val processor: (T) -> Unit,
|
||||||
|
override val icon: Int,
|
||||||
|
override val description: Int
|
||||||
|
) : Job {
|
||||||
|
override var numberOfProcessedItems: Int = 0
|
||||||
|
|
||||||
|
override fun process(notification: BatchNotification) {
|
||||||
|
notification.update(this)
|
||||||
|
var batch = provider.invoke(null)
|
||||||
|
while (batch.isNotEmpty()) {
|
||||||
|
Thread.yield()
|
||||||
|
batch.forEach {
|
||||||
|
processor.invoke(it)
|
||||||
|
Thread.yield()
|
||||||
|
}
|
||||||
|
numberOfProcessedItems += batch.size
|
||||||
|
notification.update(this)
|
||||||
|
batch = provider.invoke(batch.last())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
10
app/src/main/res/drawable/ic_notification_batch.xml
Normal file
10
app/src/main/res/drawable/ic_notification_batch.xml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFFFF"
|
||||||
|
android:pathData="M15.9,18.45C17.25,18.45 18.35,17.35 18.35,16C18.35,14.65 17.25,13.55 15.9,13.55C14.54,13.55 13.45,14.65 13.45,16C13.45,17.35 14.54,18.45 15.9,18.45M21.1,16.68L22.58,17.84C22.71,17.95 22.75,18.13 22.66,18.29L21.26,20.71C21.17,20.86 21,20.92 20.83,20.86L19.09,20.16C18.73,20.44 18.33,20.67 17.91,20.85L17.64,22.7C17.62,22.87 17.47,23 17.3,23H14.5C14.32,23 14.18,22.87 14.15,22.7L13.89,20.85C13.46,20.67 13.07,20.44 12.71,20.16L10.96,20.86C10.81,20.92 10.62,20.86 10.54,20.71L9.14,18.29C9.05,18.13 9.09,17.95 9.22,17.84L10.7,16.68L10.65,16L10.7,15.31L9.22,14.16C9.09,14.05 9.05,13.86 9.14,13.71L10.54,11.29C10.62,11.13 10.81,11.07 10.96,11.13L12.71,11.84C13.07,11.56 13.46,11.32 13.89,11.15L14.15,9.29C14.18,9.13 14.32,9 14.5,9H17.3C17.47,9 17.62,9.13 17.64,9.29L17.91,11.15C18.33,11.32 18.73,11.56 19.09,11.84L20.83,11.13C21,11.07 21.17,11.13 21.26,11.29L22.66,13.71C22.75,13.86 22.71,14.05 22.58,14.16L21.1,15.31L21.15,16L21.1,16.68M6.69,8.07C7.56,8.07 8.26,7.37 8.26,6.5C8.26,5.63 7.56,4.92 6.69,4.92A1.58,1.58 0 0,0 5.11,6.5C5.11,7.37 5.82,8.07 6.69,8.07M10.03,6.94L11,7.68C11.07,7.75 11.09,7.87 11.03,7.97L10.13,9.53C10.08,9.63 9.96,9.67 9.86,9.63L8.74,9.18L8,9.62L7.81,10.81C7.79,10.92 7.7,11 7.59,11H5.79C5.67,11 5.58,10.92 5.56,10.81L5.4,9.62L4.64,9.18L3.5,9.63C3.41,9.67 3.3,9.63 3.24,9.53L2.34,7.97C2.28,7.87 2.31,7.75 2.39,7.68L3.34,6.94L3.31,6.5L3.34,6.06L2.39,5.32C2.31,5.25 2.28,5.13 2.34,5.03L3.24,3.47C3.3,3.37 3.41,3.33 3.5,3.37L4.63,3.82L5.4,3.38L5.56,2.19C5.58,2.08 5.67,2 5.79,2H7.59C7.7,2 7.79,2.08 7.81,2.19L8,3.38L8.74,3.82L9.86,3.37C9.96,3.33 10.08,3.37 10.13,3.47L11.03,5.03C11.09,5.13 11.07,5.25 11,5.32L10.03,6.06L10.06,6.5L10.03,6.94Z" />
|
||||||
|
</vector>
|
@ -140,4 +140,5 @@ As an alternative you could configure a trusted node in the settings, but as of
|
|||||||
<string name="emulate_conversations">Guess conversations</string>
|
<string name="emulate_conversations">Guess conversations</string>
|
||||||
<string name="emulate_conversations_summary">Use subject to determine which messages belong together. The order will likely be wrong.</string>
|
<string name="emulate_conversations_summary">Use subject to determine which messages belong together. The order will likely be wrong.</string>
|
||||||
<string name="emulate_conversations_initialize">Group existing messages by subject</string>
|
<string name="emulate_conversations_initialize">Group existing messages by subject</string>
|
||||||
|
<string name="emulate_conversations_batch">Grouping existing messages by subject</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
Reference in New Issue
Block a user