Periodic backups
parent
beb17ef442
commit
beba818f57
@ -0,0 +1,74 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.backup
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.activity.result.ActivityResultCallback
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.preference.Preference
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.backup.DIR_BACKUPS
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.tryLaunch
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class PeriodicalBackupSettingsFragment : BasePreferenceFragment(R.string.periodic_backups),
|
||||||
|
ActivityResultCallback<Uri?>, SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private val outputSelectCall = registerForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocumentTree(),
|
||||||
|
this,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||||
|
addPreferencesFromResource(R.xml.pref_backup_periodic)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
settings.subscribe(this)
|
||||||
|
bindOutputSummary()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||||
|
return when (preference.key) {
|
||||||
|
AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT -> outputSelectCall.tryLaunch(null)
|
||||||
|
else -> super.onPreferenceTreeClick(preference)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
settings.unsubscribe(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(result: Uri?) {
|
||||||
|
if (result != null) {
|
||||||
|
settings.periodicalBackupOutput = result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
|
||||||
|
when (key) {
|
||||||
|
AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT -> bindOutputSummary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun bindOutputSummary() {
|
||||||
|
val preference = findPreference<Preference>(AppSettings.KEY_BACKUP_PERIODICAL_OUTPUT) ?: return
|
||||||
|
viewLifecycleScope.launch {
|
||||||
|
preference.summary = withContext(Dispatchers.Default) {
|
||||||
|
val value = settings.periodicalBackupOutput
|
||||||
|
value?.toString() ?: preference.context.run {
|
||||||
|
getExternalFilesDir(DIR_BACKUPS) ?: File(filesDir, DIR_BACKUPS)
|
||||||
|
}.path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.backup
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.hilt.work.HiltWorker
|
||||||
|
import androidx.work.Constraints
|
||||||
|
import androidx.work.CoroutineWorker
|
||||||
|
import androidx.work.ExistingPeriodicWorkPolicy
|
||||||
|
import androidx.work.PeriodicWorkRequestBuilder
|
||||||
|
import androidx.work.WorkManager
|
||||||
|
import androidx.work.WorkerParameters
|
||||||
|
import androidx.work.await
|
||||||
|
import dagger.Reusable
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import okio.buffer
|
||||||
|
import okio.sink
|
||||||
|
import okio.source
|
||||||
|
import org.koitharu.kotatsu.core.backup.BackupRepository
|
||||||
|
import org.koitharu.kotatsu.core.backup.BackupZipOutput
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.awaitUniqueWorkInfoByName
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.writeAllCancellable
|
||||||
|
import org.koitharu.kotatsu.settings.work.PeriodicWorkScheduler
|
||||||
|
import org.koitharu.kotatsu.suggestions.ui.SuggestionsWorker
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltWorker
|
||||||
|
class PeriodicalBackupWorker @AssistedInject constructor(
|
||||||
|
@Assisted appContext: Context,
|
||||||
|
@Assisted params: WorkerParameters,
|
||||||
|
private val repository: BackupRepository,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) : CoroutineWorker(appContext, params) {
|
||||||
|
|
||||||
|
override suspend fun doWork(): Result {
|
||||||
|
val file = BackupZipOutput(applicationContext).use { backup ->
|
||||||
|
backup.put(repository.createIndex())
|
||||||
|
backup.put(repository.dumpHistory())
|
||||||
|
backup.put(repository.dumpCategories())
|
||||||
|
backup.put(repository.dumpFavourites())
|
||||||
|
backup.put(repository.dumpBookmarks())
|
||||||
|
backup.put(repository.dumpSettings())
|
||||||
|
backup.finish()
|
||||||
|
backup.file
|
||||||
|
}
|
||||||
|
return settings.periodicalBackupOutput?.let {
|
||||||
|
applicationContext.contentResolver.openOutputStream(it)?.use { output ->
|
||||||
|
file.source().use { input ->
|
||||||
|
output.sink().buffer().writeAllCancellable(input)
|
||||||
|
}
|
||||||
|
Result.success()
|
||||||
|
} ?: Result.failure()
|
||||||
|
} ?: Result.success()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Reusable
|
||||||
|
class Scheduler @Inject constructor(
|
||||||
|
private val workManager: WorkManager,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) : PeriodicWorkScheduler {
|
||||||
|
|
||||||
|
override suspend fun schedule() {
|
||||||
|
val constraints = Constraints.Builder()
|
||||||
|
.setRequiresStorageNotLow(true)
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
constraints.setRequiresDeviceIdle(true)
|
||||||
|
}
|
||||||
|
val request = PeriodicWorkRequestBuilder<SuggestionsWorker>(
|
||||||
|
settings.periodicalBackupFrequency,
|
||||||
|
TimeUnit.HOURS,
|
||||||
|
).setConstraints(constraints.build())
|
||||||
|
.addTag(TAG)
|
||||||
|
.build()
|
||||||
|
workManager
|
||||||
|
.enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.UPDATE, request)
|
||||||
|
.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun unschedule() {
|
||||||
|
workManager
|
||||||
|
.cancelUniqueWork(TAG)
|
||||||
|
.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun isScheduled(): Boolean {
|
||||||
|
return workManager
|
||||||
|
.awaitUniqueWorkInfoByName(TAG)
|
||||||
|
.any { !it.state.isFinished }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private companion object {
|
||||||
|
|
||||||
|
const val TAG = "backups"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.preference.PreferenceScreen
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<SwitchPreferenceCompat
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="backup_periodic"
|
||||||
|
android:layout="@layout/preference_toggle_header"
|
||||||
|
android:title="@string/periodic_backups_enable" />
|
||||||
|
|
||||||
|
<ListPreference
|
||||||
|
android:defaultValue="7"
|
||||||
|
android:dependency="backup_periodic"
|
||||||
|
android:entries="@array/backup_frequency"
|
||||||
|
android:entryValues="@array/values_backup_frequency"
|
||||||
|
android:key="backup_periodic_freq"
|
||||||
|
android:title="@string/backup_frequency"
|
||||||
|
app:useSimpleSummaryProvider="true" />
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:dependency="backup_periodic"
|
||||||
|
android:key="backup_periodic_output"
|
||||||
|
android:title="@string/backups_output_directory" />
|
||||||
|
|
||||||
|
</androidx.preference.PreferenceScreen>
|
||||||
Loading…
Reference in New Issue