mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-19 22:29:15 +01:00
Fix activity leaks in backup, restore dialogs and properly handle db transactions
This commit is contained in:
parent
4ebb3a894d
commit
a4313d388d
@ -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.backup.models.Backup.VERSION
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.ui.setting.SettingsBackupFragment
|
import eu.kanade.tachiyomi.ui.setting.SettingsBackupFragment
|
||||||
|
import eu.kanade.tachiyomi.util.AndroidComponentUtil
|
||||||
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
import eu.kanade.tachiyomi.util.sendLocalBroadcast
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||||
@ -61,6 +62,10 @@ class BackupCreateService : IntentService(NAME) {
|
|||||||
}
|
}
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isRunning(context: Context): Boolean {
|
||||||
|
return AndroidComponentUtil.isServiceRunning(context, BackupCreateService::class.java)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val backupManager by lazy { BackupManager(this) }
|
private val backupManager by lazy { BackupManager(this) }
|
||||||
|
@ -35,6 +35,8 @@ import uy.kohesive.injekt.injectLazy
|
|||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,6 +121,8 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
private val db: DatabaseHelper by injectLazy()
|
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.
|
* 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(
|
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
|
PowerManager.PARTIAL_WAKE_LOCK, "BackupRestoreService:WakeLock")
|
||||||
wakeLock.acquire()
|
wakeLock.acquire()
|
||||||
|
executor = Executors.newSingleThreadExecutor()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -135,6 +140,7 @@ class BackupRestoreService : Service() {
|
|||||||
*/
|
*/
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
subscription?.unsubscribe()
|
subscription?.unsubscribe()
|
||||||
|
executor.shutdown() // must be called after unsubscribe
|
||||||
if (wakeLock.isHeld) {
|
if (wakeLock.isHeld) {
|
||||||
wakeLock.release()
|
wakeLock.release()
|
||||||
}
|
}
|
||||||
@ -159,53 +165,19 @@ class BackupRestoreService : Service() {
|
|||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
if (intent == null) return Service.START_NOT_STICKY
|
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.
|
// Unsubscribe from any previous subscription if needed.
|
||||||
subscription?.unsubscribe()
|
subscription?.unsubscribe()
|
||||||
|
|
||||||
val startTime = System.currentTimeMillis()
|
subscription = Observable.using(
|
||||||
subscription = Observable.defer {
|
{ db.lowLevel().beginTransaction() },
|
||||||
// Get URI
|
{ getRestoreObservable(file).doOnNext{ db.lowLevel().setTransactionSuccessful() } },
|
||||||
val uri = Uri.parse(intent.getStringExtra(EXTRA_URI))
|
{ executor.execute { db.lowLevel().endTransaction() } })
|
||||||
// Get file from Uri
|
.doAfterTerminate { stopSelf(startId) }
|
||||||
val file = UniFile.fromUri(this, uri)
|
.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
|
return Service.START_NOT_STICKY
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,7 +187,9 @@ class BackupRestoreService : Service() {
|
|||||||
* @param file restore file
|
* @param file restore file
|
||||||
* @return [Observable<Manga>]
|
* @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 reader = JsonReader(file.openInputStream().bufferedReader())
|
||||||
val json = JsonParser().parse(reader).asJsonObject
|
val json = JsonParser().parse(reader).asJsonObject
|
||||||
|
|
||||||
@ -228,6 +202,8 @@ class BackupRestoreService : Service() {
|
|||||||
val mangasJson = json.get(MANGAS).asJsonArray
|
val mangasJson = json.get(MANGAS).asJsonArray
|
||||||
|
|
||||||
restoreAmount = mangasJson.size() + 1 // +1 for categories
|
restoreAmount = mangasJson.size() + 1 // +1 for categories
|
||||||
|
restoreProgress = 0
|
||||||
|
errors.clear()
|
||||||
|
|
||||||
// Restore categories
|
// Restore categories
|
||||||
json.get(CATEGORIES)?.let {
|
json.get(CATEGORIES)?.let {
|
||||||
@ -256,6 +232,31 @@ class BackupRestoreService : Service() {
|
|||||||
Observable.just(manga)
|
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
|
package eu.kanade.tachiyomi.ui.setting
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -25,6 +26,7 @@ import eu.kanade.tachiyomi.util.*
|
|||||||
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
import eu.kanade.tachiyomi.widget.CustomLayoutPickerActivity
|
||||||
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
import eu.kanade.tachiyomi.widget.preference.IntListPreference
|
||||||
import net.xpece.android.support.preference.Preference
|
import net.xpece.android.support.preference.Preference
|
||||||
|
import rx.subscriptions.Subscriptions
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -149,7 +151,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||||||
sendIntent.putExtra(Intent.EXTRA_STREAM, file.uri)
|
sendIntent.putExtra(Intent.EXTRA_STREAM, file.uri)
|
||||||
startActivity(Intent.createChooser(sendIntent, ""))
|
startActivity(Intent.createChooser(sendIntent, ""))
|
||||||
}
|
}
|
||||||
.show()
|
.safeShow()
|
||||||
|
|
||||||
}
|
}
|
||||||
ACTION_SET_PROGRESS_DIALOG -> {
|
ACTION_SET_PROGRESS_DIALOG -> {
|
||||||
@ -194,7 +196,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||||||
}
|
}
|
||||||
materialDialog.dismiss()
|
materialDialog.dismiss()
|
||||||
}
|
}
|
||||||
.show()
|
.safeShow()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ACTION_ERROR_BACKUP_DIALOG -> {
|
ACTION_ERROR_BACKUP_DIALOG -> {
|
||||||
@ -210,19 +212,28 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
|
||||||
context.unregisterLocalReceiver(receiver)
|
|
||||||
super.onPause()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
super.onStart()
|
super.onStart()
|
||||||
context.registerLocalReceiver(receiver, IntentFilter(INTENT_FILTER))
|
context.registerLocalReceiver(receiver, IntentFilter(INTENT_FILTER))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
context.unregisterLocalReceiver(receiver)
|
||||||
|
super.onPause()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedState: Bundle?) {
|
override fun onViewCreated(view: View, savedState: Bundle?) {
|
||||||
super.onViewCreated(view, savedState)
|
super.onViewCreated(view, savedState)
|
||||||
|
|
||||||
|
if (savedState != null) {
|
||||||
|
if (BackupRestoreService.isRunning(context)) {
|
||||||
|
restoreDialog.safeShow()
|
||||||
|
}
|
||||||
|
else if (BackupCreateService.isRunning(context)) {
|
||||||
|
backupDialog.safeShow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(activity as BaseActivity).requestPermissionsOnMarshmallow()
|
(activity as BaseActivity).requestPermissionsOnMarshmallow()
|
||||||
|
|
||||||
// Set onClickListeners
|
// Set onClickListeners
|
||||||
@ -268,7 +279,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||||||
.itemsDisabledIndices(0)
|
.itemsDisabledIndices(0)
|
||||||
.positiveText(getString(R.string.action_create))
|
.positiveText(getString(R.string.action_create))
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.show()
|
.safeShow()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -359,7 +370,7 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||||||
val dir = data.data.path
|
val dir = data.data.path
|
||||||
val file = File(dir, Backup.getDefaultFilename())
|
val file = File(dir, Backup.getDefaultFilename())
|
||||||
|
|
||||||
backupDialog.show()
|
backupDialog.safeShow()
|
||||||
BackupCreateService.makeBackup(context, file.toURI().toString(), backup_flags)
|
BackupCreateService.makeBackup(context, file.toURI().toString(), backup_flags)
|
||||||
} else {
|
} else {
|
||||||
val uri = data.data
|
val uri = data.data
|
||||||
@ -369,45 +380,43 @@ class SettingsBackupFragment : SettingsFragment() {
|
|||||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
val file = UniFile.fromUri(context, uri)
|
val file = UniFile.fromUri(context, uri)
|
||||||
|
|
||||||
backupDialog.show()
|
backupDialog.safeShow()
|
||||||
BackupCreateService.makeBackup(context, file.uri.toString(), backup_flags)
|
BackupCreateService.makeBackup(context, file.uri.toString(), backup_flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
BACKUP_RESTORE -> if (data != null && resultCode == Activity.RESULT_OK) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
val uri = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
|
||||||
val uri = Uri.fromFile(File(data.data.path))
|
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()
|
|
||||||
} else {
|
} else {
|
||||||
val uri = data.data
|
val uri = data.data
|
||||||
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||||
|
|
||||||
context.contentResolver.takePersistableUriPermission(uri, flags)
|
context.contentResolver.takePersistableUriPermission(uri, flags)
|
||||||
val file = UniFile.fromUri(context, uri)
|
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 { materialDialog, _ ->
|
|
||||||
materialDialog.dismiss()
|
|
||||||
restoreDialog.show()
|
|
||||||
BackupRestoreService.start(context, file.uri.toString())
|
|
||||||
}
|
|
||||||
.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user