Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 61 additions & 48 deletions app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupNotifier.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,47 @@ import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock

class BackupNotifier(private val context: Context) {

private val lock = ReentrantLock()

private val preferences: SecurityPreferences by injectLazy()

private val progressNotificationBuilder = context.notificationBuilder(
Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS,
) {
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
setSmallIcon(R.drawable.ic_mihon)
setAutoCancel(false)
setOngoing(true)
setOnlyAlertOnce(true)
private val largeIcon by lazy {
BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
}

private val completeNotificationBuilder = context.notificationBuilder(
Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE,
) {
setLargeIcon(BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher))
setSmallIcon(R.drawable.ic_mihon)
setAutoCancel(false)
private val completeNotificationBuilder by lazy {
context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_COMPLETE) {
setLargeIcon(largeIcon)
setSmallIcon(R.drawable.ic_mihon)
setAutoCancel(false)
}
}

private fun newProgressBuilder(): NotificationCompat.Builder {
return context.notificationBuilder(Notifications.CHANNEL_BACKUP_RESTORE_PROGRESS) {
setLargeIcon(largeIcon)
setSmallIcon(R.drawable.ic_mihon)
setAutoCancel(false)
setOngoing(true)
setOnlyAlertOnce(true)
}
}

private var progressNotificationBuilder: NotificationCompat.Builder? = null

private fun NotificationCompat.Builder.show(id: Int) {
context.notify(id, build())
}

fun showBackupProgress(): NotificationCompat.Builder {
val builder = with(progressNotificationBuilder) {
setContentTitle(context.stringResource(MR.strings.creating_backup))

setProgress(0, 0, true)
}
val builder = newProgressBuilder()
.setContentTitle(context.stringResource(MR.strings.creating_backup))
.setProgress(0, 0, true)

builder.show(Notifications.ID_BACKUP_PROGRESS)

Expand Down Expand Up @@ -91,34 +99,39 @@ class BackupNotifier(private val context: Context) {
content: String = "",
progress: Int = 0,
maxAmount: Int = 100,
sync: Boolean = false,
isSync: Boolean = false,
): NotificationCompat.Builder {
val builder = with(progressNotificationBuilder) {
val contentTitle = if (sync) {
context.stringResource(MR.strings.syncing_library)
} else {
context.stringResource(MR.strings.restoring_backup)
}
setContentTitle(contentTitle)

if (!preferences.hideNotificationContent().get()) {
setContentText(content)
lock.withLock {
val builder = (
progressNotificationBuilder ?: newProgressBuilder().also {
progressNotificationBuilder = it
}
)
with(builder) {
setContentTitle(
if (isSync) {
context.stringResource(MR.strings.syncing_library)
} else {
context.stringResource(MR.strings.restoring_backup)
},
)
setProgress(maxAmount, progress, false)
setOnlyAlertOnce(true)
clearActions()
addAction(
R.drawable.ic_close_24dp,
context.stringResource(MR.strings.action_cancel),
NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS),
)
if (!preferences.hideNotificationContent().get() && content.isNotEmpty()) {
setContentText(content)
} else if (preferences.hideNotificationContent().get()) {
setContentText(null)
}
show(Notifications.ID_RESTORE_PROGRESS)
}

setProgress(maxAmount, progress, false)
setOnlyAlertOnce(true)

clearActions()
addAction(
R.drawable.ic_close_24dp,
context.stringResource(MR.strings.action_cancel),
NotificationReceiver.cancelRestorePendingBroadcast(context, Notifications.ID_RESTORE_PROGRESS),
)
return builder
}

builder.show(Notifications.ID_RESTORE_PROGRESS)

return builder
}

fun showRestoreError(error: String?) {
Expand All @@ -137,16 +150,16 @@ class BackupNotifier(private val context: Context) {
errorCount: Int,
path: String?,
file: String?,
sync: Boolean,
isSync: Boolean,
) {
val contentTitle = if (sync) {
context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)

val contentTitle = if (isSync) {
context.stringResource(MR.strings.library_sync_complete)
} else {
context.stringResource(MR.strings.restore_completed)
}

context.cancelNotification(Notifications.ID_RESTORE_PROGRESS)

val timeString = context.stringResource(
MR.strings.restore_duration,
TimeUnit.MILLISECONDS.toMinutes(time),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@ import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceRestorer
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.launch
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.i18n.MR
import java.io.File
import java.text.SimpleDateFormat
import java.util.Collections
import java.util.Date
import java.util.Locale
import java.util.concurrent.Executors
import java.util.concurrent.atomic.AtomicInteger

class BackupRestorer(
private val context: Context,
Expand All @@ -37,8 +43,13 @@ class BackupRestorer(
) {

private var restoreAmount = 0
private var restoreProgress = 0
private val errors = mutableListOf<Pair<Date, String>>()
private var restoreProgress = AtomicInteger()
private val errors = Collections.synchronizedList(mutableListOf<Pair<Date, String>>())
private val dispatcher = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors(),
).asCoroutineDispatcher()

private val mangaProgressBatch = Runtime.getRuntime().availableProcessors() * 8

/**
* Mapping of source ID to source name from backup data
Expand All @@ -48,19 +59,27 @@ class BackupRestorer(
suspend fun restore(uri: Uri, options: RestoreOptions) {
val startTime = System.currentTimeMillis()

restoreFromFile(uri, options)

val time = System.currentTimeMillis() - startTime

val logFile = writeErrorLog()

notifier.showRestoreComplete(
time,
errors.size,
logFile.parent,
logFile.name,
isSync,
)
try {
restoreFromFile(uri, options)

val time = System.currentTimeMillis() - startTime

val logFile = writeErrorLog()

notifier.showRestoreComplete(
time,
errors.size,
logFile.parent,
logFile.name,
isSync,
)
} finally {
try {
dispatcher.close()
} catch (_: Exception) {
// ignore
}
}
}

private suspend fun restoreFromFile(uri: Uri, options: RestoreOptions) {
Expand Down Expand Up @@ -107,14 +126,14 @@ class BackupRestorer(
}
}

private fun CoroutineScope.restoreCategories(backupCategories: List<BackupCategory>) = launch {
private fun CoroutineScope.restoreCategories(backupCategories: List<BackupCategory>) = launch(dispatcher) {
ensureActive()
categoriesRestorer(backupCategories)

restoreProgress += 1
restoreProgress.incrementAndGet()
notifier.showRestoreProgress(
context.stringResource(MR.strings.categories),
restoreProgress,
restoreProgress.get(),
restoreAmount,
isSync,
)
Expand All @@ -123,58 +142,73 @@ class BackupRestorer(
private fun CoroutineScope.restoreManga(
backupMangas: List<BackupManga>,
backupCategories: List<BackupCategory>,
) = launch {
mangaRestorer.sortByNew(backupMangas)
.forEach {
) = launch(dispatcher) {
val sortedMangas = mangaRestorer.sortByNew(backupMangas)
sortedMangas.map {
async {
ensureActive()

try {
mangaRestorer.restore(it, backupCategories)
} catch (e: Exception) {
val sourceName = sourceMapping[it.source] ?: it.source.toString()
errors.add(Date() to "${it.title} [$sourceName]: ${e.message}")
} finally {
val currentProgress = restoreProgress.incrementAndGet()
if (currentProgress == restoreAmount || currentProgress % mangaProgressBatch == 0) {
notifier.showRestoreProgress(it.title, currentProgress, restoreAmount, isSync)
}
}

restoreProgress += 1
notifier.showRestoreProgress(it.title, restoreProgress, restoreAmount, isSync)
}
}.awaitAll()

val finalProgress = restoreProgress.get()
if (finalProgress < restoreAmount) {
notifier.showRestoreProgress(
context.stringResource(MR.strings.restoring_backup),
finalProgress,
restoreAmount,
isSync,
)
}
}

private fun CoroutineScope.restoreAppPreferences(
preferences: List<BackupPreference>,
categories: List<BackupCategory>?,
) = launch {
) = launch(dispatcher) {
ensureActive()
preferenceRestorer.restoreApp(
preferences,
categories,
)

restoreProgress += 1
restoreProgress.incrementAndGet()
notifier.showRestoreProgress(
context.stringResource(MR.strings.app_settings),
restoreProgress,
restoreProgress.get(),
restoreAmount,
isSync,
)
}

private fun CoroutineScope.restoreSourcePreferences(preferences: List<BackupSourcePreferences>) = launch {
private fun CoroutineScope.restoreSourcePreferences(preferences: List<BackupSourcePreferences>) = launch(
dispatcher,
) {
ensureActive()
preferenceRestorer.restoreSource(preferences)

restoreProgress += 1
restoreProgress.incrementAndGet()
notifier.showRestoreProgress(
context.stringResource(MR.strings.source_settings),
restoreProgress,
restoreProgress.get(),
restoreAmount,
isSync,
)
}

private fun CoroutineScope.restoreExtensionRepos(
backupExtensionRepo: List<BackupExtensionRepos>,
) = launch {
) = launch(dispatcher) {
backupExtensionRepo
.forEach {
ensureActive()
Expand All @@ -185,10 +219,10 @@ class BackupRestorer(
errors.add(Date() to "Error Adding Repo: ${it.name} : ${e.message}")
}

restoreProgress += 1
restoreProgress.incrementAndGet()
notifier.showRestoreProgress(
context.stringResource(MR.strings.extensionRepo_settings),
restoreProgress,
restoreProgress.get(),
restoreAmount,
isSync,
)
Expand All @@ -208,7 +242,7 @@ class BackupRestorer(
}
return file
}
} catch (e: Exception) {
} catch (_: Exception) {
// Empty
}
return File("")
Expand Down
Loading