diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupActivity.kt b/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupActivity.kt index e780974a..39f13f78 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupActivity.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupActivity.kt @@ -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() 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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupViewModel.kt b/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupViewModel.kt index 05a51b25..00cdffef 100644 --- a/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupViewModel.kt +++ b/app/src/main/java/code/name/monkey/retromusic/activities/backup/BackupViewModel.kt @@ -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>() @@ -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) + } + } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt index 276e95a9..c8f80411 100644 --- a/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt +++ b/app/src/main/java/code/name/monkey/retromusic/adapter/backup/BackupAdapter.kt @@ -12,6 +12,7 @@ import java.io.File class BackupAdapter( val context: Context, var dataSet: MutableList, + val backupClickedListener: BackupClickedListener ) : RecyclerView.Adapter() { 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) + } } \ No newline at end of file diff --git a/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt b/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt index 80589b57..b2712afc 100644 --- a/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt +++ b/app/src/main/java/code/name/monkey/retromusic/helper/BackupHelper.kt @@ -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() 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) \ No newline at end of file