Fix activity leaks in backup, restore dialogs and properly handle db transactions
This commit is contained in:
parent
4ebb3a894d
commit
a4313d388d
3 changed files with 94 additions and 79 deletions
|
@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.data.backup.models.Backup.MANGAS
|
|||
import eu.kanade.tachiyomi.data.backup.models.Backup.VERSION
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.ui.setting.SettingsBackupFragment
|
||||
import eu.kanade.tachiyomi.util.AndroidComponentUtil
|
||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
||||
import timber.log.Timber
|
||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
|
@ -61,6 +62,10 @@ class BackupCreateService : IntentService(NAME) {
|
|||
}
|
||||
context.startService(intent)
|
||||
}
|
||||
|
||||
fun isRunning(context: Context): Boolean {
|
||||
return AndroidComponentUtil.isServiceRunning(context, BackupCreateService::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
private val backupManager by lazy { BackupManager(this) }
|
||||
|
|
|
@ -35,6 +35,8 @@ import uy.kohesive.injekt.injectLazy
|
|||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||
|
||||
/**
|
||||
|
@ -119,6 +121,8 @@ class BackupRestoreService : Service() {
|
|||
*/
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
|
||||
lateinit var executor: ExecutorService
|
||||
|
||||
/**
|
||||
* Method called when the service is created. It injects dependencies and acquire the wake lock.
|
||||
*/
|
||||
|
@ -127,6 +131,7 @@ class BackupRestoreService : Service() {
|
|||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
|
||||
wakeLock.acquire()
|
||||
executor = Executors.newSingleThreadExecutor()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -135,6 +140,7 @@ class BackupRestoreService : Service() {
|
|||
*/
|
||||
override fun onDestroy() {
|
||||
subscription?.unsubscribe()
|
||||
executor.shutdown() // must be called after unsubscribe
|
||||
if (wakeLock.isHeld) {
|
||||
wakeLock.release()
|
||||
}
|
||||
|
@ -159,53 +165,19 @@ class BackupRestoreService : Service() {
|
|||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
if (intent == null) return Service.START_NOT_STICKY
|
||||
|
||||
val file = UniFile.fromUri(this, Uri.parse(intent.getStringExtra(EXTRA_URI)))
|
||||
|
||||
// Unsubscribe from any previous subscription if needed.
|
||||
subscription?.unsubscribe()
|
||||
|
||||
val startTime = System.currentTimeMillis()
|
||||
subscription = Observable.defer {
|
||||
// Get URI
|
||||
val uri = Uri.parse(intent.getStringExtra(EXTRA_URI))
|
||||
// Get file from Uri
|
||||
val file = UniFile.fromUri(this, uri)
|
||||
subscription = Observable.using(
|
||||
{ db.lowLevel().beginTransaction() },
|
||||
{ getRestoreObservable(file).doOnNext{ db.lowLevel().setTransactionSuccessful() } },
|
||||
{ executor.execute { db.lowLevel().endTransaction() } })
|
||||
.doAfterTerminate { stopSelf(startId) }
|
||||
.subscribeOn(Schedulers.from(executor))
|
||||
.subscribe()
|
||||
|
||||
// Clear errors
|
||||
errors.clear()
|
||||
|
||||
// Reset progress
|
||||
restoreProgress = 0
|
||||
|
||||
db.lowLevel().beginTransaction()
|
||||
getRestoreObservable(file)
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe({
|
||||
}, { error ->
|
||||
db.lowLevel().endTransaction()
|
||||
Timber.e(error)
|
||||
writeErrorLog()
|
||||
val errorIntent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
|
||||
putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_ERROR_RESTORE_DIALOG)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERROR_MESSAGE, error.message)
|
||||
}
|
||||
sendLocalBroadcast(errorIntent)
|
||||
stopSelf(startId)
|
||||
}, {
|
||||
db.lowLevel().setTransactionSuccessful()
|
||||
db.lowLevel().endTransaction()
|
||||
val endTime = System.currentTimeMillis()
|
||||
val time = endTime - startTime
|
||||
val file = writeErrorLog()
|
||||
val completeIntent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
|
||||
putExtra(SettingsBackupFragment.EXTRA_TIME, time)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERRORS, errors.size)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERROR_FILE_PATH, file.parent)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERROR_FILE, file.name)
|
||||
putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_RESTORE_COMPLETED_DIALOG)
|
||||
}
|
||||
sendLocalBroadcast(completeIntent)
|
||||
stopSelf(startId)
|
||||
})
|
||||
return Service.START_NOT_STICKY
|
||||
}
|
||||
|
||||
|
@ -215,7 +187,9 @@ class BackupRestoreService : Service() {
|
|||
* @param file restore file
|
||||
* @return [Observable<Manga>]
|
||||
*/
|
||||
private fun getRestoreObservable(file: UniFile): Observable<Manga> {
|
||||
private fun getRestoreObservable(file: UniFile): Observable<List<Manga>> {
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
val reader = JsonReader(file.openInputStream().bufferedReader())
|
||||
val json = JsonParser().parse(reader).asJsonObject
|
||||
|
||||
|
@ -228,6 +202,8 @@ class BackupRestoreService : Service() {
|
|||
val mangasJson = json.get(MANGAS).asJsonArray
|
||||
|
||||
restoreAmount = mangasJson.size() + 1 // +1 for categories
|
||||
restoreProgress = 0
|
||||
errors.clear()
|
||||
|
||||
// Restore categories
|
||||
json.get(CATEGORIES)?.let {
|
||||
|
@ -256,6 +232,31 @@ class BackupRestoreService : Service() {
|
|||
Observable.just(manga)
|
||||
}
|
||||
}
|
||||
.toList()
|
||||
.doOnNext {
|
||||
val endTime = System.currentTimeMillis()
|
||||
val time = endTime - startTime
|
||||
val logFile = writeErrorLog()
|
||||
val completeIntent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
|
||||
putExtra(SettingsBackupFragment.EXTRA_TIME, time)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERRORS, errors.size)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERROR_FILE_PATH, logFile.parent)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERROR_FILE, logFile.name)
|
||||
putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_RESTORE_COMPLETED_DIALOG)
|
||||
}
|
||||
sendLocalBroadcast(completeIntent)
|
||||
|
||||
}
|
||||
.doOnError { error ->
|
||||
Timber.e(error)
|
||||
writeErrorLog()
|
||||
val errorIntent = Intent(SettingsBackupFragment.INTENT_FILTER).apply {
|
||||
putExtra(SettingsBackupFragment.ACTION, SettingsBackupFragment.ACTION_ERROR_RESTORE_DIALOG)
|
||||
putExtra(SettingsBackupFragment.EXTRA_ERROR_MESSAGE, error.message)
|
||||
}
|
||||
sendLocalBroadcast(errorIntent)
|
||||
}
|
||||
.onErrorReturn { emptyList() }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package eu.kanade.tachiyomi.ui.setting
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.Dialog
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
@ -25,6 +26,7 @@ import eu.kanade.tachiyomi.util.*
|
|||
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
||||
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
||||
import net.xpece.android.support.preference.Preference
|
||||
import rx.subscriptions.Subscriptions
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -149,7 +151,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||
sendIntent.putExtra(Intent.EXTRA_STREAM, file.uri)
|
||||
startActivity(Intent.createChooser(sendIntent, ""))
|
||||
}
|
||||
.show()
|
||||
.safeShow()
|
||||
|
||||
}
|
||||
ACTION_SET_PROGRESS_DIALOG -> {
|
||||
|
@ -194,7 +196,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||
}
|
||||
materialDialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
.safeShow()
|
||||
}
|
||||
}
|
||||
ACTION_ERROR_BACKUP_DIALOG -> {
|
||||
|
@ -210,19 +212,28 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
context.unregisterLocalReceiver(receiver)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
context.registerLocalReceiver(receiver, IntentFilter(INTENT_FILTER))
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
context.unregisterLocalReceiver(receiver)
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||
super.onViewCreated(view, savedState)
|
||||
|
||||
if (savedState != null) {
|
||||
if (BackupRestoreService.isRunning(context)) {
|
||||
restoreDialog.safeShow()
|
||||
}
|
||||
else if (BackupCreateService.isRunning(context)) {
|
||||
backupDialog.safeShow()
|
||||
}
|
||||
}
|
||||
|
||||
(activity as BaseActivity).requestPermissionsOnMarshmallow()
|
||||
|
||||
// Set onClickListeners
|
||||
|
@ -268,7 +279,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||
.itemsDisabledIndices(0)
|
||||
.positiveText(getString(R.string.action_create))
|
||||
.negativeText(android.R.string.cancel)
|
||||
.show()
|
||||
.safeShow()
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -359,7 +370,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||
val dir = data.data.path
|
||||
val file = File(dir, Backup.getDefaultFilename())
|
||||
|
||||
backupDialog.show()
|
||||
backupDialog.safeShow()
|
||||
BackupCreateService.makeBackup(context, file.toURI().toString(), backup_flags)
|
||||
} else {
|
||||
val uri = data.data
|
||||
|
@ -369,45 +380,43 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
val file = UniFile.fromUri(context, uri)
|
||||
|
||||
backupDialog.show()
|
||||
backupDialog.safeShow()
|
||||
BackupCreateService.makeBackup(context, file.uri.toString(), backup_flags)
|
||||
}
|
||||
}
|
||||
BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
val uri = Uri.fromFile(File(data.data.path))
|
||||
|
||||
MaterialDialog.Builder(context)
|
||||
.title(getString(R.string.pref_restore_backup))
|
||||
.content(getString(R.string.backup_restore_content))
|
||||
.positiveText(getString(R.string.action_restore))
|
||||
.onPositive { materialDialog, _ ->
|
||||
materialDialog.dismiss()
|
||||
restoreDialog.show()
|
||||
BackupRestoreService.start(context, uri.toString())
|
||||
}
|
||||
.show()
|
||||
val uri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||
Uri.fromFile(File(data.data.path))
|
||||
} else {
|
||||
val uri = data.data
|
||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
|
||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||
val file = UniFile.fromUri(context, uri)
|
||||
|
||||
MaterialDialog.Builder(context)
|
||||
.title(getString(R.string.pref_restore_backup))
|
||||
.content(getString(R.string.backup_restore_content))
|
||||
.positiveText(getString(R.string.action_restore))
|
||||
.onPositive { materialDialog, _ ->
|
||||
materialDialog.dismiss()
|
||||
restoreDialog.show()
|
||||
BackupRestoreService.start(context, file.uri.toString())
|
||||
}
|
||||
.show()
|
||||
UniFile.fromUri(context, uri).uri
|
||||
}
|
||||
|
||||
MaterialDialog.Builder(context)
|
||||
.title(getString(R.string.pref_restore_backup))
|
||||
.content(getString(R.string.backup_restore_content))
|
||||
.positiveText(getString(R.string.action_restore))
|
||||
.onPositive { _, _ ->
|
||||
restoreDialog.safeShow()
|
||||
BackupRestoreService.start(context, uri.toString())
|
||||
}
|
||||
.safeShow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun MaterialDialog.Builder.safeShow(): Dialog {
|
||||
return build().safeShow()
|
||||
}
|
||||
|
||||
fun Dialog.safeShow(): Dialog {
|
||||
subscriptions += Subscriptions.create { dismiss() }
|
||||
show()
|
||||
return this
|
||||
}
|
||||
|
||||
}
|
Reference in a new issue