Improve android AutoBackup support
This commit is contained in:
@@ -8,23 +8,23 @@
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"
|
||||
tools:ignore="ScopedStorage" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:name="org.koitharu.kotatsu.KotatsuApp"
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:backupAgent="org.koitharu.kotatsu.settings.backup.AppBackupAgent"
|
||||
android:dataExtractionRules="@xml/backup_rules"
|
||||
android:fullBackupContent="@xml/backup_content"
|
||||
android:fullBackupOnly="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Kotatsu"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:ignore="UnusedAttribute">
|
||||
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.main.ui.MainActivity"
|
||||
android:exported="true">
|
||||
@@ -104,6 +104,7 @@
|
||||
android:windowSoftInputMode="adjustResize" />
|
||||
<activity
|
||||
android:name="org.koitharu.kotatsu.download.ui.DownloadsActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:label="@string/downloads" />
|
||||
<activity android:name="org.koitharu.kotatsu.image.ui.ImageActivity"/>
|
||||
|
||||
|
||||
@@ -1,28 +1,9 @@
|
||||
package org.koitharu.kotatsu.core.db
|
||||
|
||||
import androidx.room.Room
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.dsl.module
|
||||
import org.koitharu.kotatsu.core.db.migrations.*
|
||||
|
||||
val databaseModule
|
||||
get() = module {
|
||||
single {
|
||||
Room.databaseBuilder(
|
||||
androidContext(),
|
||||
MangaDatabase::class.java,
|
||||
"kotatsu-db"
|
||||
).addMigrations(
|
||||
Migration1To2(),
|
||||
Migration2To3(),
|
||||
Migration3To4(),
|
||||
Migration4To5(),
|
||||
Migration5To6(),
|
||||
Migration6To7(),
|
||||
Migration7To8(),
|
||||
Migration8To9(),
|
||||
).addCallback(
|
||||
DatabasePrePopulateCallback(androidContext().resources)
|
||||
).build()
|
||||
}
|
||||
single { MangaDatabase.create(androidContext()) }
|
||||
}
|
||||
@@ -1,9 +1,12 @@
|
||||
package org.koitharu.kotatsu.core.db
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import org.koitharu.kotatsu.core.db.dao.*
|
||||
import org.koitharu.kotatsu.core.db.entity.*
|
||||
import org.koitharu.kotatsu.core.db.migrations.*
|
||||
import org.koitharu.kotatsu.favourites.data.FavouriteCategoriesDao
|
||||
import org.koitharu.kotatsu.favourites.data.FavouriteCategoryEntity
|
||||
import org.koitharu.kotatsu.favourites.data.FavouriteEntity
|
||||
@@ -40,4 +43,24 @@ abstract class MangaDatabase : RoomDatabase() {
|
||||
abstract val trackLogsDao: TrackLogsDao
|
||||
|
||||
abstract val suggestionDao: SuggestionDao
|
||||
|
||||
companion object {
|
||||
|
||||
fun create(context: Context): MangaDatabase = Room.databaseBuilder(
|
||||
context,
|
||||
MangaDatabase::class.java,
|
||||
"kotatsu-db"
|
||||
).addMigrations(
|
||||
Migration1To2(),
|
||||
Migration2To3(),
|
||||
Migration3To4(),
|
||||
Migration4To5(),
|
||||
Migration5To6(),
|
||||
Migration6To7(),
|
||||
Migration7To8(),
|
||||
Migration8To9(),
|
||||
).addCallback(
|
||||
DatabasePrePopulateCallback(context.resources)
|
||||
).build()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.koitharu.kotatsu.settings.backup
|
||||
|
||||
import android.app.backup.BackupAgent
|
||||
import android.app.backup.BackupDataInput
|
||||
import android.app.backup.BackupDataOutput
|
||||
import android.app.backup.FullBackupDataOutput
|
||||
import android.os.ParcelFileDescriptor
|
||||
import kotlinx.coroutines.NonCancellable
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.koitharu.kotatsu.core.backup.BackupArchive
|
||||
import org.koitharu.kotatsu.core.backup.BackupEntry
|
||||
import org.koitharu.kotatsu.core.backup.BackupRepository
|
||||
import org.koitharu.kotatsu.core.backup.RestoreRepository
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import java.io.*
|
||||
|
||||
class AppBackupAgent : BackupAgent() {
|
||||
|
||||
override fun onBackup(
|
||||
oldState: ParcelFileDescriptor?,
|
||||
data: BackupDataOutput?,
|
||||
newState: ParcelFileDescriptor?
|
||||
) = Unit
|
||||
|
||||
override fun onRestore(
|
||||
data: BackupDataInput?,
|
||||
appVersionCode: Int,
|
||||
newState: ParcelFileDescriptor?
|
||||
) = Unit
|
||||
|
||||
override fun onFullBackup(data: FullBackupDataOutput) {
|
||||
super.onFullBackup(data)
|
||||
val file = createBackupFile()
|
||||
try {
|
||||
fullBackupFile(file, data)
|
||||
} finally {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRestoreFile(
|
||||
data: ParcelFileDescriptor,
|
||||
size: Long,
|
||||
destination: File?,
|
||||
type: Int,
|
||||
mode: Long,
|
||||
mtime: Long
|
||||
) {
|
||||
if (destination?.name?.endsWith(".bak") == true) {
|
||||
restoreBackupFile(data.fileDescriptor, size)
|
||||
destination.delete()
|
||||
} else {
|
||||
super.onRestoreFile(data, size, destination, type, mode, mtime)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createBackupFile() = runBlocking {
|
||||
val repository = BackupRepository(MangaDatabase.create(applicationContext))
|
||||
val backup = BackupArchive.createNew(this@AppBackupAgent)
|
||||
backup.put(repository.createIndex())
|
||||
backup.put(repository.dumpHistory())
|
||||
backup.put(repository.dumpCategories())
|
||||
backup.put(repository.dumpFavourites())
|
||||
backup.flush()
|
||||
backup.cleanup()
|
||||
backup.file
|
||||
}
|
||||
|
||||
private fun restoreBackupFile(fd: FileDescriptor, size: Long) {
|
||||
val repository = RestoreRepository(MangaDatabase.create(applicationContext))
|
||||
val tempFile = File.createTempFile("backup_", ".tmp")
|
||||
FileInputStream(fd).use { input ->
|
||||
tempFile.outputStream().use { output ->
|
||||
input.copyLimitedTo(output, size)
|
||||
}
|
||||
}
|
||||
val backup = BackupArchive(tempFile)
|
||||
try {
|
||||
runBlocking {
|
||||
backup.unpack()
|
||||
repository.upsertHistory(backup.getEntry(BackupEntry.HISTORY))
|
||||
repository.upsertCategories(backup.getEntry(BackupEntry.CATEGORIES))
|
||||
repository.upsertFavourites(backup.getEntry(BackupEntry.FAVOURITES))
|
||||
}
|
||||
} finally {
|
||||
runBlocking(NonCancellable) {
|
||||
backup.cleanup()
|
||||
}
|
||||
tempFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
private fun InputStream.copyLimitedTo(out: OutputStream, limit: Long) {
|
||||
var bytesCopied: Long = 0
|
||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE.coerceAtMost(limit.toInt()))
|
||||
var bytes = read(buffer)
|
||||
while (bytes >= 0) {
|
||||
out.write(buffer, 0, bytes)
|
||||
bytesCopied += bytes
|
||||
val bytesLeft = (limit - bytesCopied).toInt()
|
||||
if (bytesLeft <= 0) {
|
||||
break
|
||||
}
|
||||
bytes = read(buffer, 0, buffer.size.coerceAtMost(bytesLeft))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.koitharu.kotatsu.utils
|
||||
|
||||
import androidx.annotation.CheckResult
|
||||
import androidx.annotation.WorkerThread
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.runInterruptible
|
||||
@@ -44,7 +43,6 @@ open class MutableZipFile(val file: File) {
|
||||
dir.deleteRecursively()
|
||||
}
|
||||
|
||||
@CheckResult
|
||||
suspend fun flush(): Boolean = runInterruptible(Dispatchers.IO) {
|
||||
val tempFile = File(file.path + ".tmp")
|
||||
if (tempFile.exists()) {
|
||||
|
||||
6
app/src/main/res/xml/backup_content.xml
Normal file
6
app/src/main/res/xml/backup_content.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<include
|
||||
domain="sharedpref"
|
||||
path="." />
|
||||
</full-backup-content>
|
||||
@@ -1,2 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content />
|
||||
6
app/src/main/res/xml/backup_rules.xml
Normal file
6
app/src/main/res/xml/backup_rules.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<data-extraction-rules>
|
||||
<cloud-backup disableIfNoEncryptionCapabilities="false">
|
||||
<include domain="sharedpref" path="."/>
|
||||
</cloud-backup>
|
||||
</data-extraction-rules>
|
||||
Reference in New Issue
Block a user