diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eb198b8ea..837ee29cb 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -8,23 +8,23 @@
-
+
@@ -104,6 +104,7 @@
android:windowSoftInputMode="adjustResize" />
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/DatabaseModule.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/DatabaseModule.kt
index dc390f0f3..dadbb05eb 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/db/DatabaseModule.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/db/DatabaseModule.kt
@@ -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()) }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt
index 075e6d02a..010a2653f 100644
--- a/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/core/db/MangaDatabase.kt
@@ -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()
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/backup/AppBackupAgent.kt b/app/src/main/java/org/koitharu/kotatsu/settings/backup/AppBackupAgent.kt
new file mode 100644
index 000000000..9b59a983c
--- /dev/null
+++ b/app/src/main/java/org/koitharu/kotatsu/settings/backup/AppBackupAgent.kt
@@ -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))
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt b/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt
index b785a62d2..01eaf7118 100644
--- a/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt
+++ b/app/src/main/java/org/koitharu/kotatsu/utils/MutableZipFile.kt
@@ -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()) {
diff --git a/app/src/main/res/xml/backup_content.xml b/app/src/main/res/xml/backup_content.xml
new file mode 100644
index 000000000..f2c5989ed
--- /dev/null
+++ b/app/src/main/res/xml/backup_content.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/xml/backup_descriptor.xml b/app/src/main/res/xml/backup_descriptor.xml
deleted file mode 100644
index 1b0854f7d..000000000
--- a/app/src/main/res/xml/backup_descriptor.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..5c0b063ba
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file