Improve android AutoBackup support
This commit is contained in:
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user