diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 010ee07f0..d5664e443 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -209,10 +209,6 @@
android:name=".data.updater.AppUpdateService"
android:exported="false" />
-
-
diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt
index 9cf2bbe72..b28af0912 100644
--- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt
+++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsBackupScreen.kt
@@ -41,9 +41,9 @@ import eu.kanade.presentation.more.settings.Preference
import eu.kanade.presentation.util.collectAsState
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupConst
-import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
+import eu.kanade.tachiyomi.data.backup.BackupCreateJob
import eu.kanade.tachiyomi.data.backup.BackupFileValidator
-import eu.kanade.tachiyomi.data.backup.BackupRestoreService
+import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
import eu.kanade.tachiyomi.data.backup.models.Backup
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.DeviceUtil
@@ -93,7 +93,7 @@ object SettingsBackupScreen : SearchableSettings {
Intent.FLAG_GRANT_READ_URI_PERMISSION or
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
)
- BackupCreatorJob.startNow(context, it, flag)
+ BackupCreateJob.startNow(context, it, flag)
}
flag = 0
}
@@ -119,7 +119,7 @@ object SettingsBackupScreen : SearchableSettings {
subtitle = stringResource(R.string.pref_create_backup_summ),
onClick = {
scope.launch {
- if (!BackupCreatorJob.isManualJobRunning(context)) {
+ if (!BackupCreateJob.isManualJobRunning(context)) {
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
}
@@ -271,7 +271,7 @@ object SettingsBackupScreen : SearchableSettings {
confirmButton = {
TextButton(
onClick = {
- BackupRestoreService.start(context, err.uri)
+ BackupRestoreJob.start(context, err.uri)
onDismissRequest()
},
) {
@@ -301,7 +301,7 @@ object SettingsBackupScreen : SearchableSettings {
}
if (results.missingSources.isEmpty() && results.missingTrackers.isEmpty()) {
- BackupRestoreService.start(context, it)
+ BackupRestoreJob.start(context, it)
return@rememberLauncherForActivityResult
}
@@ -313,7 +313,7 @@ object SettingsBackupScreen : SearchableSettings {
title = stringResource(R.string.pref_restore_backup),
subtitle = stringResource(R.string.pref_restore_backup_summ),
onClick = {
- if (!BackupRestoreService.isRunning(context)) {
+ if (!BackupRestoreJob.isRunning(context)) {
if (DeviceUtil.isMiui && DeviceUtil.isMiuiOptimizationDisabled()) {
context.toast(R.string.restore_miui_warning, Toast.LENGTH_LONG)
}
@@ -364,7 +364,7 @@ object SettingsBackupScreen : SearchableSettings {
168 to stringResource(R.string.update_weekly),
),
onValueChanged = {
- BackupCreatorJob.setupTask(context, it)
+ BackupCreateJob.setupTask(context, it)
true
},
),
diff --git a/app/src/main/java/eu/kanade/presentation/util/Resources.kt b/app/src/main/java/eu/kanade/presentation/util/Resources.kt
index 44d9968b9..5b7bc9948 100644
--- a/app/src/main/java/eu/kanade/presentation/util/Resources.kt
+++ b/app/src/main/java/eu/kanade/presentation/util/Resources.kt
@@ -11,11 +11,11 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.toBitmap
/**
- * Create a BitmapPainter from an drawable resource.
- *
- * > Only use this if [androidx.compose.ui.res.painterResource] doesn't work.
+ * Create a BitmapPainter from a drawable resource.
+ * Use this only if [androidx.compose.ui.res.painterResource] doesn't work.
*
* @param id the resource identifier
+ *
* @return the bitmap associated with the resource
*/
@Composable
diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt
index 08447b980..d21a48721 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt
@@ -8,7 +8,7 @@ import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.tachiyomi.core.security.SecurityPreferences
-import eu.kanade.tachiyomi.data.backup.BackupCreatorJob
+import eu.kanade.tachiyomi.data.backup.BackupCreateJob
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.track.TrackManager
@@ -57,7 +57,7 @@ object Migrations {
// Always set up background tasks to ensure they're running
LibraryUpdateJob.setupTask(context)
- BackupCreatorJob.setupTask(context)
+ BackupCreateJob.setupTask(context)
// Fresh install
if (oldVersion == 0) {
@@ -99,7 +99,7 @@ object Migrations {
if (oldVersion < 43) {
// Restore jobs after migrating from Evernote's job scheduler to WorkManager.
LibraryUpdateJob.setupTask(context)
- BackupCreatorJob.setupTask(context)
+ BackupCreateJob.setupTask(context)
}
if (oldVersion < 44) {
// Reset sorting preference if using removed sort by source
@@ -249,7 +249,7 @@ object Migrations {
}
}
if (oldVersion < 76) {
- BackupCreatorJob.setupTask(context)
+ BackupCreateJob.setupTask(context)
}
if (oldVersion < 77) {
val oldReaderTap = prefs.getBoolean("reader_tap", false)
@@ -284,7 +284,7 @@ object Migrations {
}
if (backupPreferences.backupInterval().get() == 0) {
backupPreferences.backupInterval().set(12)
- BackupCreatorJob.setupTask(context)
+ BackupCreateJob.setupTask(context)
}
}
if (oldVersion < 85) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt
similarity index 93%
rename from app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
rename to app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt
index dfbff66b6..d3495c578 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreatorJob.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupCreateJob.kt
@@ -23,7 +23,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.TimeUnit
-class BackupCreatorJob(private val context: Context, workerParams: WorkerParameters) :
+class BackupCreateJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
private val notifier = BackupNotifier(context)
@@ -41,7 +41,6 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" }
}
- context.notificationManager.notify(Notifications.ID_BACKUP_PROGRESS, notifier.showBackupProgress().build())
return try {
val location = BackupManager(context).createBackup(uri, flags, isAutoBackup)
if (!isAutoBackup) notifier.showBackupComplete(UniFile.fromUri(context, location.toUri()))
@@ -70,7 +69,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
val interval = prefInterval ?: backupPreferences.backupInterval().get()
val workManager = WorkManager.getInstance(context)
if (interval > 0) {
- val request = PeriodicWorkRequestBuilder(
+ val request = PeriodicWorkRequestBuilder(
interval.toLong(),
TimeUnit.HOURS,
10,
@@ -92,7 +91,7 @@ class BackupCreatorJob(private val context: Context, workerParams: WorkerParamet
LOCATION_URI_KEY to uri.toString(),
BACKUP_FLAGS_KEY to flags,
)
- val request = OneTimeWorkRequestBuilder()
+ val request = OneTimeWorkRequestBuilder()
.addTag(TAG_MANUAL)
.setInputData(inputData)
.build()
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt
index 613f66bf2..c38fb2f2d 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt
@@ -67,9 +67,7 @@ class BackupNotifier(private val context: Context) {
setContentTitle(context.getString(R.string.backup_created))
setContentText(unifile.filePath ?: unifile.name)
- // Clear old actions if they exist
clearActions()
-
addAction(
R.drawable.ic_share_24dp,
context.getString(R.string.action_share),
@@ -91,12 +89,10 @@ class BackupNotifier(private val context: Context) {
setProgress(maxAmount, progress, false)
setOnlyAlertOnce(true)
- // Clear old actions if they exist
clearActions()
-
addAction(
R.drawable.ic_close_24dp,
- context.getString(R.string.action_stop),
+ context.getString(R.string.action_cancel),
NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS),
)
}
@@ -132,9 +128,7 @@ class BackupNotifier(private val context: Context) {
setContentTitle(context.getString(R.string.restore_completed))
setContentText(context.resources.getQuantityString(R.plurals.restore_completed_message, errorCount, timeString, errorCount))
- // Clear old actions if they exist
clearActions()
-
if (errorCount > 0 && !path.isNullOrEmpty() && !file.isNullOrEmpty()) {
val destFile = File(path, file)
val uri = destFile.getUriCompat(context)
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt
new file mode 100644
index 000000000..4ec24679c
--- /dev/null
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreJob.kt
@@ -0,0 +1,87 @@
+package eu.kanade.tachiyomi.data.backup
+
+import android.content.Context
+import android.net.Uri
+import androidx.core.net.toUri
+import androidx.work.CoroutineWorker
+import androidx.work.ExistingWorkPolicy
+import androidx.work.ForegroundInfo
+import androidx.work.OneTimeWorkRequestBuilder
+import androidx.work.WorkInfo
+import androidx.work.WorkManager
+import androidx.work.WorkerParameters
+import androidx.work.workDataOf
+import eu.kanade.tachiyomi.R
+import eu.kanade.tachiyomi.data.notification.Notifications
+import eu.kanade.tachiyomi.util.system.notificationManager
+import kotlinx.coroutines.CancellationException
+import logcat.LogPriority
+import tachiyomi.core.util.system.logcat
+
+class BackupRestoreJob(private val context: Context, workerParams: WorkerParameters) :
+ CoroutineWorker(context, workerParams) {
+
+ private val notifier = BackupNotifier(context)
+
+ override suspend fun doWork(): Result {
+ val uri = inputData.getString(LOCATION_URI_KEY)?.toUri()
+ ?: return Result.failure()
+
+ try {
+ setForeground(getForegroundInfo())
+ } catch (e: IllegalStateException) {
+ logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" }
+ }
+
+ return try {
+ val restorer = BackupRestorer(context, notifier)
+ restorer.restoreBackup(uri)
+ Result.success()
+ } catch (e: Exception) {
+ if (e is CancellationException) {
+ notifier.showRestoreError(context.getString(R.string.restoring_backup_canceled))
+ Result.success()
+ } else {
+ logcat(LogPriority.ERROR, e)
+ notifier.showRestoreError(e.message)
+ Result.failure()
+ }
+ } finally {
+ context.notificationManager.cancel(Notifications.ID_RESTORE_PROGRESS)
+ }
+ }
+
+ override suspend fun getForegroundInfo(): ForegroundInfo {
+ return ForegroundInfo(
+ Notifications.ID_RESTORE_PROGRESS,
+ notifier.showRestoreProgress().build(),
+ )
+ }
+
+ companion object {
+ fun isRunning(context: Context): Boolean {
+ val list = WorkManager.getInstance(context).getWorkInfosByTag(TAG).get()
+ return list.find { it.state == WorkInfo.State.RUNNING } != null
+ }
+
+ fun start(context: Context, uri: Uri) {
+ val inputData = workDataOf(
+ LOCATION_URI_KEY to uri.toString(),
+ )
+ val request = OneTimeWorkRequestBuilder()
+ .addTag(TAG)
+ .setInputData(inputData)
+ .build()
+ WorkManager.getInstance(context)
+ .enqueueUniqueWork(TAG, ExistingWorkPolicy.KEEP, request)
+ }
+
+ fun stop(context: Context) {
+ WorkManager.getInstance(context).cancelUniqueWork(TAG)
+ }
+ }
+}
+
+private const val TAG = "BackupRestore"
+
+private const val LOCATION_URI_KEY = "location_uri" // String
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt
deleted file mode 100644
index 28b796586..000000000
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestoreService.kt
+++ /dev/null
@@ -1,141 +0,0 @@
-package eu.kanade.tachiyomi.data.backup
-
-import android.app.Service
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.os.IBinder
-import android.os.PowerManager
-import androidx.core.content.ContextCompat
-import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.notification.Notifications
-import eu.kanade.tachiyomi.util.system.acquireWakeLock
-import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat
-import eu.kanade.tachiyomi.util.system.isServiceRunning
-import kotlinx.coroutines.CoroutineExceptionHandler
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.launch
-import logcat.LogPriority
-import tachiyomi.core.util.system.logcat
-
-/**
- * Restores backup.
- */
-class BackupRestoreService : Service() {
-
- companion object {
-
- /**
- * Returns the status of the service.
- *
- * @param context the application context.
- * @return true if the service is running, false otherwise.
- */
- fun isRunning(context: Context): Boolean =
- context.isServiceRunning(BackupRestoreService::class.java)
-
- /**
- * Starts a service to restore a backup from Json
- *
- * @param context context of application
- * @param uri path of Uri
- */
- fun start(context: Context, uri: Uri) {
- if (isRunning(context)) return
-
- val intent = Intent(context, BackupRestoreService::class.java).apply {
- putExtra(BackupConst.EXTRA_URI, uri)
- }
- ContextCompat.startForegroundService(context, intent)
- }
-
- /**
- * Stops the service.
- *
- * @param context the application context.
- */
- fun stop(context: Context) {
- context.stopService(Intent(context, BackupRestoreService::class.java))
-
- BackupNotifier(context).showRestoreError(context.getString(R.string.restoring_backup_canceled))
- }
- }
-
- /**
- * Wake lock that will be held until the service is destroyed.
- */
- private lateinit var wakeLock: PowerManager.WakeLock
-
- private lateinit var scope: CoroutineScope
- private var restorer: BackupRestorer? = null
- private lateinit var notifier: BackupNotifier
-
- override fun onCreate() {
- scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
- notifier = BackupNotifier(this)
- wakeLock = acquireWakeLock(javaClass.name)
-
- startForeground(Notifications.ID_RESTORE_PROGRESS, notifier.showRestoreProgress().build())
- }
-
- override fun stopService(name: Intent?): Boolean {
- destroyJob()
- return super.stopService(name)
- }
-
- override fun onDestroy() {
- destroyJob()
- }
-
- private fun destroyJob() {
- restorer?.job?.cancel()
- scope.cancel()
- if (wakeLock.isHeld) {
- wakeLock.release()
- }
- }
-
- /**
- * This method needs to be implemented, but it's not used/needed.
- */
- override fun onBind(intent: Intent): IBinder? = null
-
- /**
- * Method called when the service receives an intent.
- *
- * @param intent the start intent from.
- * @param flags the flags of the command.
- * @param startId the start id of this command.
- * @return the start value of the command.
- */
- override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
- val uri = intent?.getParcelableExtraCompat(BackupConst.EXTRA_URI) ?: return START_NOT_STICKY
-
- // Cancel any previous job if needed.
- restorer?.job?.cancel()
-
- restorer = BackupRestorer(this, notifier)
-
- val handler = CoroutineExceptionHandler { _, exception ->
- logcat(LogPriority.ERROR, exception)
- restorer?.writeErrorLog()
-
- notifier.showRestoreError(exception.message)
- stopSelf(startId)
- }
- val job = scope.launch(handler) {
- if (restorer?.restoreBackup(uri) == false) {
- notifier.showRestoreError(getString(R.string.restoring_backup_canceled))
- }
- }
- job.invokeOnCompletion {
- stopSelf(startId)
- }
- restorer?.job = job
-
- return START_NOT_STICKY
- }
-}
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt
index 29880a3e1..0ff096497 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupRestorer.kt
@@ -9,8 +9,8 @@ import eu.kanade.tachiyomi.data.backup.models.BackupManga
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.util.BackupUtil
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
-import kotlinx.coroutines.Job
-import okio.source
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.isActive
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.track.model.Track
@@ -24,8 +24,6 @@ class BackupRestorer(
private val notifier: BackupNotifier,
) {
- var job: Job? = null
-
private var backupManager = BackupManager(context)
private var restoreAmount = 0
@@ -56,7 +54,7 @@ class BackupRestorer(
return true
}
- fun writeErrorLog(): File {
+ private fun writeErrorLog(): File {
try {
if (errors.isNotEmpty()) {
val file = context.createFileInCacheDir("tachiyomi_restore.txt")
@@ -90,18 +88,18 @@ class BackupRestorer(
val backupMaps = backup.backupBrokenSources.map { BackupSource(it.name, it.sourceId) } + backup.backupSources
sourceMapping = backupMaps.associate { it.sourceId to it.name }
- // Restore individual manga
- backup.backupManga.forEach {
- if (job?.isActive != true) {
- return false
+ return coroutineScope {
+ // Restore individual manga
+ backup.backupManga.forEach {
+ if (!isActive) {
+ return@coroutineScope false
+ }
+
+ restoreManga(it, backup.backupCategories)
}
-
- restoreManga(it, backup.backupCategories)
+ // TODO: optionally trigger online library + tracker update
+ true
}
-
- // TODO: optionally trigger online library + tracker update
-
- return true
}
private suspend fun restoreCategories(backupCategories: List) {
diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
index 5ad86ad50..2428da302 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt
@@ -6,10 +6,9 @@ import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
-import androidx.core.content.ContextCompat
import androidx.core.net.toUri
import eu.kanade.tachiyomi.R
-import eu.kanade.tachiyomi.data.backup.BackupRestoreService
+import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.updater.AppUpdateService
@@ -82,10 +81,7 @@ class NotificationReceiver : BroadcastReceiver() {
"application/x-protobuf+gzip",
intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1),
)
- ACTION_CANCEL_RESTORE -> cancelRestore(
- context,
- intent.getIntExtra(EXTRA_NOTIFICATION_ID, -1),
- )
+ ACTION_CANCEL_RESTORE -> cancelRestore(context)
// Cancel library update and dismiss notification
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
// Cancel downloading app update
@@ -206,11 +202,9 @@ class NotificationReceiver : BroadcastReceiver() {
* Method called when user wants to stop a backup restore job.
*
* @param context context of application
- * @param notificationId id of notification
*/
- private fun cancelRestore(context: Context, notificationId: Int) {
- BackupRestoreService.stop(context)
- ContextCompat.getMainExecutor(context).execute { dismissNotification(context, notificationId) }
+ private fun cancelRestore(context: Context) {
+ BackupRestoreJob.stop(context)
}
/**
diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml
index 88ac37d83..b6da8239f 100644
--- a/i18n/src/main/res/values/strings.xml
+++ b/i18n/src/main/res/values/strings.xml
@@ -86,7 +86,6 @@
Delete category
Edit cover
View chapters
- Stop
Pause
Previous chapter
Next chapter