Use WakeLock for background operations
This commit is contained in:
@@ -24,8 +24,10 @@ import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
|||||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.powerManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -51,12 +53,14 @@ class AutoFixService : CoroutineIntentService() {
|
|||||||
val ids = requireNotNull(intent.getLongArrayExtra(DATA_IDS))
|
val ids = requireNotNull(intent.getLongArrayExtra(DATA_IDS))
|
||||||
startForeground(this)
|
startForeground(this)
|
||||||
for (mangaId in ids) {
|
for (mangaId in ids) {
|
||||||
val result = runCatchingCancellable {
|
powerManager.withPartialWakeLock(TAG) {
|
||||||
autoFixUseCase.invoke(mangaId)
|
val result = runCatchingCancellable {
|
||||||
}
|
autoFixUseCase.invoke(mangaId)
|
||||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
}
|
||||||
val notification = buildNotification(result)
|
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||||
notificationManager.notify(TAG, startId, notification)
|
val notification = buildNotification(result)
|
||||||
|
notificationManager.notify(TAG, startId, notification)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
|||||||
import org.xmlpull.v1.XmlPullParser
|
import org.xmlpull.v1.XmlPullParser
|
||||||
import org.xmlpull.v1.XmlPullParserException
|
import org.xmlpull.v1.XmlPullParserException
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
val Context.activityManager: ActivityManager?
|
val Context.activityManager: ActivityManager?
|
||||||
@@ -228,3 +229,21 @@ fun Context.restartApplication() {
|
|||||||
startActivity(intent)
|
startActivity(intent)
|
||||||
activity?.finishAndRemoveTask()
|
activity?.finishAndRemoveTask()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal inline fun <R> PowerManager?.withPartialWakeLock(tag: String, body: (PowerManager.WakeLock?) -> R): R {
|
||||||
|
val wakeLock = newPartialWakeLock(tag)
|
||||||
|
return try {
|
||||||
|
wakeLock?.acquire(TimeUnit.HOURS.toMillis(1))
|
||||||
|
body(wakeLock)
|
||||||
|
} finally {
|
||||||
|
wakeLock?.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PowerManager?.newPartialWakeLock(tag: String): PowerManager.WakeLock? {
|
||||||
|
return if (this != null && isWakeLockLevelSupported(PowerManager.PARTIAL_WAKE_LOCK)) {
|
||||||
|
newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,9 +23,11 @@ import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
|||||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.powerManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
import org.koitharu.kotatsu.core.util.ext.toBitmapOrNull
|
||||||
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
|
||||||
import org.koitharu.kotatsu.local.data.importer.SingleMangaImporter
|
import org.koitharu.kotatsu.local.data.importer.SingleMangaImporter
|
||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||||
@@ -50,12 +52,14 @@ class ImportService : CoroutineIntentService() {
|
|||||||
override suspend fun IntentJobContext.processIntent(intent: Intent) {
|
override suspend fun IntentJobContext.processIntent(intent: Intent) {
|
||||||
val uri = requireNotNull(intent.getStringExtra(DATA_URI)?.toUriOrNull()) { "No input uri" }
|
val uri = requireNotNull(intent.getStringExtra(DATA_URI)?.toUriOrNull()) { "No input uri" }
|
||||||
startForeground(this)
|
startForeground(this)
|
||||||
val result = runCatchingCancellable {
|
powerManager.withPartialWakeLock(TAG) {
|
||||||
importer.import(uri).manga
|
val result = runCatchingCancellable {
|
||||||
}
|
importer.import(uri).manga
|
||||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
}
|
||||||
val notification = buildNotification(result)
|
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||||
notificationManager.notify(TAG, startId, notification)
|
val notification = buildNotification(result)
|
||||||
|
notificationManager.notify(TAG, startId, notification)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
|||||||
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
|
import org.koitharu.kotatsu.core.util.ext.getParcelableExtraCompat
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.powerManager
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
|
||||||
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
import org.koitharu.kotatsu.local.data.LocalMangaRepository
|
||||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||||
@@ -44,12 +46,14 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun IntentJobContext.processIntent(intent: Intent) {
|
override suspend fun IntentJobContext.processIntent(intent: Intent) {
|
||||||
|
startForeground(this)
|
||||||
val manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
|
val manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
|
||||||
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
|
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
|
||||||
startForeground(this)
|
powerManager.withPartialWakeLock(TAG) {
|
||||||
val mangaWithChapters = localMangaRepository.getDetails(manga)
|
val mangaWithChapters = localMangaRepository.getDetails(manga)
|
||||||
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
|
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
|
||||||
localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))
|
localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun IntentJobContext.onError(error: Throwable) {
|
override fun IntentJobContext.onError(error: Throwable) {
|
||||||
@@ -104,6 +108,8 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
|||||||
private const val EXTRA_MANGA = "manga"
|
private const val EXTRA_MANGA = "manga"
|
||||||
private const val EXTRA_CHAPTERS_IDS = "chapters_ids"
|
private const val EXTRA_CHAPTERS_IDS = "chapters_ids"
|
||||||
|
|
||||||
|
private const val TAG = CHANNEL_ID
|
||||||
|
|
||||||
fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>) {
|
fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>) {
|
||||||
if (chaptersIds.isEmpty()) {
|
if (chaptersIds.isEmpty()) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package org.koitharu.kotatsu.settings.backup
|
package org.koitharu.kotatsu.settings.backup
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@@ -19,20 +18,17 @@ import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
|||||||
import org.koitharu.kotatsu.core.util.progress.Progress
|
import org.koitharu.kotatsu.core.util.progress.Progress
|
||||||
import org.koitharu.kotatsu.databinding.DialogProgressBinding
|
import org.koitharu.kotatsu.databinding.DialogProgressBinding
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileOutputStream
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
||||||
|
|
||||||
private val viewModel by viewModels<BackupViewModel>()
|
private val viewModel by viewModels<BackupViewModel>()
|
||||||
|
|
||||||
private var backup: File? = null
|
|
||||||
private val saveFileContract = registerForActivityResult(
|
private val saveFileContract = registerForActivityResult(
|
||||||
ActivityResultContracts.CreateDocument("application/zip"),
|
ActivityResultContracts.CreateDocument("application/zip"),
|
||||||
) { uri ->
|
) { uri ->
|
||||||
val file = backup
|
if (uri != null) {
|
||||||
if (uri != null && file != null) {
|
viewModel.saveBackup(uri)
|
||||||
saveBackup(file, uri)
|
|
||||||
} else {
|
} else {
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
@@ -51,6 +47,7 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
|||||||
viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged)
|
viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged)
|
||||||
viewModel.onBackupDone.observeEvent(viewLifecycleOwner, this::onBackupDone)
|
viewModel.onBackupDone.observeEvent(viewLifecycleOwner, this::onBackupDone)
|
||||||
viewModel.onError.observeEvent(viewLifecycleOwner, this::onError)
|
viewModel.onError.observeEvent(viewLifecycleOwner, this::onError)
|
||||||
|
viewModel.onBackupSaved.observeEvent(viewLifecycleOwner) { onBackupSaved() }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||||
@@ -81,26 +78,14 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun onBackupDone(file: File) {
|
private fun onBackupDone(file: File) {
|
||||||
this.backup = file
|
|
||||||
if (!saveFileContract.tryLaunch(file.name)) {
|
if (!saveFileContract.tryLaunch(file.name)) {
|
||||||
Toast.makeText(requireContext(), R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveBackup(file: File, output: Uri) {
|
private fun onBackupSaved() {
|
||||||
try {
|
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_SHORT).show()
|
||||||
requireContext().contentResolver.openFileDescriptor(output, "w")?.use { fd ->
|
dismiss()
|
||||||
FileOutputStream(fd.fileDescriptor).use {
|
|
||||||
it.write(file.readBytes())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_SHORT).show()
|
|
||||||
dismiss()
|
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
throw e
|
|
||||||
} catch (e: Exception) {
|
|
||||||
onError(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package org.koitharu.kotatsu.settings.backup
|
package org.koitharu.kotatsu.settings.backup
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import org.koitharu.kotatsu.core.backup.BackupRepository
|
import org.koitharu.kotatsu.core.backup.BackupRepository
|
||||||
import org.koitharu.kotatsu.core.backup.BackupZipOutput
|
import org.koitharu.kotatsu.core.backup.BackupZipOutput
|
||||||
@@ -11,6 +14,7 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
|||||||
import org.koitharu.kotatsu.core.util.ext.call
|
import org.koitharu.kotatsu.core.util.ext.call
|
||||||
import org.koitharu.kotatsu.core.util.progress.Progress
|
import org.koitharu.kotatsu.core.util.progress.Progress
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -21,9 +25,13 @@ class BackupViewModel @Inject constructor(
|
|||||||
|
|
||||||
val progress = MutableStateFlow(Progress.INDETERMINATE)
|
val progress = MutableStateFlow(Progress.INDETERMINATE)
|
||||||
val onBackupDone = MutableEventFlow<File>()
|
val onBackupDone = MutableEventFlow<File>()
|
||||||
|
val onBackupSaved = MutableEventFlow<Unit>()
|
||||||
|
|
||||||
|
private val contentResolver: ContentResolver = context.contentResolver
|
||||||
|
private var backupFile: File? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
launchLoadingJob {
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
val file = BackupZipOutput.createTemp(context).use { backup ->
|
val file = BackupZipOutput.createTemp(context).use { backup ->
|
||||||
progress.value = Progress(0, 7)
|
progress.value = Progress(0, 7)
|
||||||
backup.put(repository.createIndex())
|
backup.put(repository.createIndex())
|
||||||
@@ -52,7 +60,20 @@ class BackupViewModel @Inject constructor(
|
|||||||
backup.finish()
|
backup.finish()
|
||||||
backup.file
|
backup.file
|
||||||
}
|
}
|
||||||
|
backupFile = file
|
||||||
onBackupDone.call(file)
|
onBackupDone.call(file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveBackup(output: Uri) {
|
||||||
|
launchLoadingJob(Dispatchers.Default) {
|
||||||
|
val file = checkNotNull(backupFile)
|
||||||
|
contentResolver.openFileDescriptor(output, "w")?.use { fd ->
|
||||||
|
FileOutputStream(fd.fileDescriptor).use {
|
||||||
|
it.write(file.readBytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onBackupSaved.call(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ import org.koitharu.kotatsu.core.ui.CoroutineIntentService
|
|||||||
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
import org.koitharu.kotatsu.core.util.ext.checkNotificationPermission
|
||||||
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
|
||||||
import org.koitharu.kotatsu.core.util.ext.getFileDisplayName
|
import org.koitharu.kotatsu.core.util.ext.getFileDisplayName
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.powerManager
|
||||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||||
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
import org.koitharu.kotatsu.core.util.ext.toUriOrNull
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
|
||||||
import org.koitharu.kotatsu.core.util.progress.Progress
|
import org.koitharu.kotatsu.core.util.progress.Progress
|
||||||
import org.koitharu.kotatsu.parsers.util.mapToArray
|
import org.koitharu.kotatsu.parsers.util.mapToArray
|
||||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||||
@@ -59,20 +61,22 @@ class RestoreService : CoroutineIntentService() {
|
|||||||
if (entries.isNullOrEmpty()) {
|
if (entries.isNullOrEmpty()) {
|
||||||
throw IllegalArgumentException("No entries specified")
|
throw IllegalArgumentException("No entries specified")
|
||||||
}
|
}
|
||||||
val result = runInterruptible(Dispatchers.IO) {
|
powerManager.withPartialWakeLock(TAG) {
|
||||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
val result = runInterruptible(Dispatchers.IO) {
|
||||||
(contentResolver.openInputStream(uri) ?: throw FileNotFoundException()).use { input ->
|
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||||
tempFile.outputStream().use { output ->
|
(contentResolver.openInputStream(uri) ?: throw FileNotFoundException()).use { input ->
|
||||||
input.copyTo(output)
|
tempFile.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
BackupZipInput.from(tempFile)
|
||||||
|
}.use { backupInput ->
|
||||||
|
restoreImpl(displayName, backupInput, entries)
|
||||||
|
}
|
||||||
|
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||||
|
val notification = buildNotification(displayName, result)
|
||||||
|
notificationManager.notify(TAG, startId, notification)
|
||||||
}
|
}
|
||||||
BackupZipInput.from(tempFile)
|
|
||||||
}.use { backupInput ->
|
|
||||||
restoreImpl(displayName, backupInput, entries)
|
|
||||||
}
|
|
||||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
|
||||||
val notification = buildNotification(displayName, result)
|
|
||||||
notificationManager.notify(TAG, startId, notification)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user