Added ability to restore
This commit is contained in:
parent
897b160834
commit
d703a05182
4 changed files with 120 additions and 8 deletions
|
@ -16,8 +16,9 @@ import com.google.android.material.shape.MaterialShapeDrawable
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
|
||||
class BackupActivity : AbsThemeActivity() {
|
||||
class BackupActivity : AbsThemeActivity(), BackupAdapter.BackupClickedListener {
|
||||
|
||||
private val backupViewModel by viewModels<BackupViewModel>()
|
||||
private var backupAdapter: BackupAdapter? = null
|
||||
|
@ -59,7 +60,7 @@ class BackupActivity : AbsThemeActivity() {
|
|||
}
|
||||
|
||||
private fun initAdapter() {
|
||||
backupAdapter = BackupAdapter(this@BackupActivity, ArrayList())
|
||||
backupAdapter = BackupAdapter(this@BackupActivity, ArrayList(), this)
|
||||
backupAdapter?.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
|
||||
override fun onChanged() {
|
||||
super.onChanged()
|
||||
|
@ -82,4 +83,10 @@ class BackupActivity : AbsThemeActivity() {
|
|||
adapter = backupAdapter
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBackupClicked(file: File) {
|
||||
lifecycleScope.launch {
|
||||
backupViewModel.restoreBackup(this@BackupActivity, file)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,11 +1,17 @@
|
|||
package code.name.monkey.retromusic.activities.backup
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import code.name.monkey.retromusic.helper.BackupHelper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
||||
class BackupViewModel : ViewModel() {
|
||||
private val backupsMutableLiveData = MutableLiveData<List<File>>()
|
||||
|
@ -14,10 +20,22 @@ class BackupViewModel : ViewModel() {
|
|||
fun loadBackups() {
|
||||
viewModelScope.launch {
|
||||
File(BackupHelper.backupRootPath).listFiles { _, name ->
|
||||
return@listFiles name.endsWith(BackupHelper.backupExt)
|
||||
return@listFiles name.endsWith(BackupHelper.BACKUP_EXTENSION)
|
||||
}?.toList()?.let {
|
||||
backupsMutableLiveData.value = it
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun restoreBackup(activity: Activity, file: File) {
|
||||
BackupHelper.restoreBackup(activity, file)
|
||||
withContext(Dispatchers.Main) {
|
||||
val intent = Intent(
|
||||
activity,
|
||||
activity::class.java
|
||||
)
|
||||
activity.startActivity(intent)
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import java.io.File
|
|||
class BackupAdapter(
|
||||
val context: Context,
|
||||
var dataSet: MutableList<File>,
|
||||
val backupClickedListener: BackupClickedListener
|
||||
) : RecyclerView.Adapter<BackupAdapter.ViewHolder>() {
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||
|
@ -35,6 +36,13 @@ class BackupAdapter(
|
|||
val title: TextView = itemView.findViewById(R.id.title)
|
||||
|
||||
init {
|
||||
itemView.setOnClickListener {
|
||||
backupClickedListener.onBackupClicked(dataSet[bindingAdapterPosition])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface BackupClickedListener {
|
||||
fun onBackupClicked(file: File)
|
||||
}
|
||||
}
|
|
@ -2,17 +2,20 @@ package code.name.monkey.retromusic.helper
|
|||
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import android.widget.Toast
|
||||
import code.name.monkey.retromusic.BuildConfig
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.*
|
||||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
object BackupHelper {
|
||||
suspend fun createBackup(context: Context) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val finalPath = backupRootPath + System.currentTimeMillis().toString() + backupExt
|
||||
val finalPath =
|
||||
backupRootPath + System.currentTimeMillis().toString() + APPEND_EXTENSION
|
||||
val zipItems = mutableListOf<ZipItem>()
|
||||
zipItems.addAll(getDatabaseZipItems(context))
|
||||
zipItems.addAll(getSettingsZipItems(context))
|
||||
|
@ -39,7 +42,7 @@ object BackupHelper {
|
|||
return context.databaseList().filter {
|
||||
it.endsWith(".db")
|
||||
}.map {
|
||||
ZipItem(context.getDatabasePath(it).absolutePath, "databases${File.separator}$it")
|
||||
ZipItem(context.getDatabasePath(it).absolutePath, "$DATABASES_PATH${File.separator}$it")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +52,7 @@ object BackupHelper {
|
|||
"${BuildConfig.APPLICATION_ID}_preferences.xml", // App settings pref path
|
||||
"$THEME_PREFS_KEY_DEFAULT.xml" // appthemehelper pref path
|
||||
).map {
|
||||
ZipItem(sharedPrefPath + it, "preferences${File.separator}$it")
|
||||
ZipItem(sharedPrefPath + it, "$SETTINGS_PATH${File.separator}$it")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -57,15 +60,91 @@ object BackupHelper {
|
|||
return context.filesDir.listFiles { _, name ->
|
||||
name.endsWith(".jpg")
|
||||
}?.map {
|
||||
ZipItem(it.absolutePath, "userImages${File.separator}${it.name}")
|
||||
ZipItem(it.absolutePath, "$IMAGES_PATH${File.separator}${it.name}")
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun restoreBackup(context: Context, file: File) {
|
||||
withContext(Dispatchers.IO) {
|
||||
ZipInputStream(FileInputStream(file)).use {
|
||||
var entry = it.nextEntry
|
||||
while (entry != null) {
|
||||
if (entry.isDatabaseEntry()) restoreDatabase(context, it, entry)
|
||||
if (entry.isPreferenceEntry()) restorePreferences(context, it, entry)
|
||||
if (entry.isImageEntry()) restoreImages(context, it, entry)
|
||||
entry = it.nextEntry
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(context, "Restore Completed Successfully", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreImages(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
|
||||
val filePath =
|
||||
context.filesDir.path + File.separator + zipEntry.getFileName()
|
||||
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
|
||||
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
var read: Int
|
||||
while (zipIn.read(bytesIn).also { read = it } != -1) {
|
||||
bos.write(bytesIn, 0, read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restorePreferences(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
|
||||
val file = File(
|
||||
context.filesDir.parent!! + File.separator + "shared_prefs" + File.separator + zipEntry.getFileName()
|
||||
)
|
||||
if (file.exists()) {
|
||||
file.delete()
|
||||
}
|
||||
BufferedOutputStream(FileOutputStream(file)).use { bos ->
|
||||
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
var read: Int
|
||||
while (zipIn.read(bytesIn).also { read = it } != -1) {
|
||||
bos.write(bytesIn, 0, read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun restoreDatabase(context: Context, zipIn: ZipInputStream, zipEntry: ZipEntry) {
|
||||
val filePath =
|
||||
context.filesDir.parent!! + File.separator + DATABASES_PATH + File.separator + zipEntry.getFileName()
|
||||
BufferedOutputStream(FileOutputStream(filePath)).use { bos ->
|
||||
val bytesIn = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||
var read: Int
|
||||
while (zipIn.read(bytesIn).also { read = it } != -1) {
|
||||
bos.write(bytesIn, 0, read)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val backupRootPath =
|
||||
Environment.getExternalStorageDirectory().toString() + "/RetroMusic/Backups/"
|
||||
const val backupExt = ".rmbak"
|
||||
const val BACKUP_EXTENSION = "zip"
|
||||
private const val APPEND_EXTENSION = ".$BACKUP_EXTENSION"
|
||||
private const val DATABASES_PATH = "databases"
|
||||
private const val SETTINGS_PATH = "prefs"
|
||||
private const val IMAGES_PATH = "userImages"
|
||||
private const val THEME_PREFS_KEY_DEFAULT = "[[kabouzeid_app-theme-helper]]"
|
||||
|
||||
private fun ZipEntry.isDatabaseEntry(): Boolean {
|
||||
return name.startsWith(DATABASES_PATH)
|
||||
}
|
||||
|
||||
private fun ZipEntry.isPreferenceEntry(): Boolean {
|
||||
return name.startsWith(SETTINGS_PATH)
|
||||
}
|
||||
|
||||
private fun ZipEntry.isImageEntry(): Boolean {
|
||||
return name.startsWith(IMAGES_PATH)
|
||||
}
|
||||
|
||||
private fun ZipEntry.getFileName(): String {
|
||||
return name.substring(name.lastIndexOf(File.separator))
|
||||
}
|
||||
}
|
||||
|
||||
data class ZipItem(val filePath: String, val zipPath: String)
|
Loading…
Reference in a new issue