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.getDisplayMessage
|
||||
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.toBitmapOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import javax.inject.Inject
|
||||
@@ -51,12 +53,14 @@ class AutoFixService : CoroutineIntentService() {
|
||||
val ids = requireNotNull(intent.getLongArrayExtra(DATA_IDS))
|
||||
startForeground(this)
|
||||
for (mangaId in ids) {
|
||||
val result = runCatchingCancellable {
|
||||
autoFixUseCase.invoke(mangaId)
|
||||
}
|
||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||
val notification = buildNotification(result)
|
||||
notificationManager.notify(TAG, startId, notification)
|
||||
powerManager.withPartialWakeLock(TAG) {
|
||||
val result = runCatchingCancellable {
|
||||
autoFixUseCase.invoke(mangaId)
|
||||
}
|
||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||
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.XmlPullParserException
|
||||
import java.io.File
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
val Context.activityManager: ActivityManager?
|
||||
@@ -228,3 +229,21 @@ fun Context.restartApplication() {
|
||||
startActivity(intent)
|
||||
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.getDisplayMessage
|
||||
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.toBitmapOrNull
|
||||
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.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
@@ -50,12 +52,14 @@ class ImportService : CoroutineIntentService() {
|
||||
override suspend fun IntentJobContext.processIntent(intent: Intent) {
|
||||
val uri = requireNotNull(intent.getStringExtra(DATA_URI)?.toUriOrNull()) { "No input uri" }
|
||||
startForeground(this)
|
||||
val result = runCatchingCancellable {
|
||||
importer.import(uri).manga
|
||||
}
|
||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||
val notification = buildNotification(result)
|
||||
notificationManager.notify(TAG, startId, notification)
|
||||
powerManager.withPartialWakeLock(TAG) {
|
||||
val result = runCatchingCancellable {
|
||||
importer.import(uri).manga
|
||||
}
|
||||
if (applicationContext.checkNotificationPermission(CHANNEL_ID)) {
|
||||
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.util.ext.getDisplayMessage
|
||||
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.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.model.LocalManga
|
||||
@@ -44,12 +46,14 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
}
|
||||
|
||||
override suspend fun IntentJobContext.processIntent(intent: Intent) {
|
||||
startForeground(this)
|
||||
val manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return
|
||||
val chaptersIds = intent.getLongArrayExtra(EXTRA_CHAPTERS_IDS)?.toSet() ?: return
|
||||
startForeground(this)
|
||||
val mangaWithChapters = localMangaRepository.getDetails(manga)
|
||||
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
|
||||
localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))
|
||||
powerManager.withPartialWakeLock(TAG) {
|
||||
val mangaWithChapters = localMangaRepository.getDetails(manga)
|
||||
localMangaRepository.deleteChapters(mangaWithChapters, chaptersIds)
|
||||
localStorageChanges.emit(LocalManga(localMangaRepository.getDetails(manga)))
|
||||
}
|
||||
}
|
||||
|
||||
override fun IntentJobContext.onError(error: Throwable) {
|
||||
@@ -104,6 +108,8 @@ class LocalChaptersRemoveService : CoroutineIntentService() {
|
||||
private const val EXTRA_MANGA = "manga"
|
||||
private const val EXTRA_CHAPTERS_IDS = "chapters_ids"
|
||||
|
||||
private const val TAG = CHANNEL_ID
|
||||
|
||||
fun start(context: Context, manga: Manga, chaptersIds: Collection<Long>) {
|
||||
if (chaptersIds.isEmpty()) {
|
||||
return
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.settings.backup
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
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.databinding.DialogProgressBinding
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
||||
|
||||
private val viewModel by viewModels<BackupViewModel>()
|
||||
|
||||
private var backup: File? = null
|
||||
private val saveFileContract = registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/zip"),
|
||||
) { uri ->
|
||||
val file = backup
|
||||
if (uri != null && file != null) {
|
||||
saveBackup(file, uri)
|
||||
if (uri != null) {
|
||||
viewModel.saveBackup(uri)
|
||||
} else {
|
||||
dismiss()
|
||||
}
|
||||
@@ -51,6 +47,7 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
||||
viewModel.progress.observe(viewLifecycleOwner, this::onProgressChanged)
|
||||
viewModel.onBackupDone.observeEvent(viewLifecycleOwner, this::onBackupDone)
|
||||
viewModel.onError.observeEvent(viewLifecycleOwner, this::onError)
|
||||
viewModel.onBackupSaved.observeEvent(viewLifecycleOwner) { onBackupSaved() }
|
||||
}
|
||||
|
||||
override fun onBuildDialog(builder: MaterialAlertDialogBuilder): MaterialAlertDialogBuilder {
|
||||
@@ -81,26 +78,14 @@ class BackupDialogFragment : AlertDialogFragment<DialogProgressBinding>() {
|
||||
}
|
||||
|
||||
private fun onBackupDone(file: File) {
|
||||
this.backup = file
|
||||
if (!saveFileContract.tryLaunch(file.name)) {
|
||||
Toast.makeText(requireContext(), R.string.operation_not_supported, Toast.LENGTH_SHORT).show()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveBackup(file: File, output: Uri) {
|
||||
try {
|
||||
requireContext().contentResolver.openFileDescriptor(output, "w")?.use { fd ->
|
||||
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)
|
||||
}
|
||||
private fun onBackupSaved() {
|
||||
Toast.makeText(requireContext(), R.string.backup_saved, Toast.LENGTH_SHORT).show()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
package org.koitharu.kotatsu.settings.backup
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.koitharu.kotatsu.core.backup.BackupRepository
|
||||
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.progress.Progress
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@@ -21,9 +25,13 @@ class BackupViewModel @Inject constructor(
|
||||
|
||||
val progress = MutableStateFlow(Progress.INDETERMINATE)
|
||||
val onBackupDone = MutableEventFlow<File>()
|
||||
val onBackupSaved = MutableEventFlow<Unit>()
|
||||
|
||||
private val contentResolver: ContentResolver = context.contentResolver
|
||||
private var backupFile: File? = null
|
||||
|
||||
init {
|
||||
launchLoadingJob {
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val file = BackupZipOutput.createTemp(context).use { backup ->
|
||||
progress.value = Progress(0, 7)
|
||||
backup.put(repository.createIndex())
|
||||
@@ -52,7 +60,20 @@ class BackupViewModel @Inject constructor(
|
||||
backup.finish()
|
||||
backup.file
|
||||
}
|
||||
backupFile = 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.getDisplayMessage
|
||||
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.toUriOrNull
|
||||
import org.koitharu.kotatsu.core.util.ext.withPartialWakeLock
|
||||
import org.koitharu.kotatsu.core.util.progress.Progress
|
||||
import org.koitharu.kotatsu.parsers.util.mapToArray
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
@@ -59,20 +61,22 @@ class RestoreService : CoroutineIntentService() {
|
||||
if (entries.isNullOrEmpty()) {
|
||||
throw IllegalArgumentException("No entries specified")
|
||||
}
|
||||
val result = runInterruptible(Dispatchers.IO) {
|
||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||
(contentResolver.openInputStream(uri) ?: throw FileNotFoundException()).use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
input.copyTo(output)
|
||||
powerManager.withPartialWakeLock(TAG) {
|
||||
val result = runInterruptible(Dispatchers.IO) {
|
||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||
(contentResolver.openInputStream(uri) ?: throw FileNotFoundException()).use { input ->
|
||||
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