Files
ShiftRing/app/src/main/java/com/example/shiftalarm/BackupManager.kt
T

179 lines
6.6 KiB
Kotlin

package com.example.shiftalarm
import android.content.Context
import android.net.Uri
import org.json.JSONArray
import org.json.JSONObject
import java.io.InputStreamReader
import java.io.OutputStreamWriter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* Handles data backup and restoration (Database + SharedPreferences).
* Format: JSON
*/
object BackupManager {
suspend fun backupData(context: Context, uri: Uri, dao: ShiftDao) = withContext(Dispatchers.IO) {
val overrides = dao.getAllOverrides()
val memos = dao.getAllMemos()
val json = JSONObject()
// 1. Backup Overrides
val overrideArray = JSONArray()
overrides.forEach {
overrideArray.put(JSONObject().apply {
put("date", it.date)
put("shift", it.shift)
put("team", it.team)
put("factory", it.factory)
})
}
json.put("overrides", overrideArray)
// 1.5 Backup Custom Alarms
val customAlarms = dao.getAllCustomAlarms()
val customAlarmArray = JSONArray()
customAlarms.forEach {
customAlarmArray.put(JSONObject().apply {
put("time", it.time)
put("shiftType", it.shiftType)
put("isEnabled", it.isEnabled)
put("soundUri", it.soundUri)
put("snoozeInterval", it.snoozeInterval)
put("snoozeRepeat", it.snoozeRepeat)
})
}
json.put("custom_alarms_v2", customAlarmArray)
// 2. Backup Memos
val memoArray = JSONArray()
memos.forEach {
memoArray.put(JSONObject().apply {
put("date", it.date)
put("content", it.content)
})
}
json.put("memos", memoArray)
// 3. Backup Settings (SharedPreferences)
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
val settings = JSONObject()
prefs.all.forEach { (key, value) ->
if (value is String) settings.put(key, value)
else if (value is Boolean) settings.put(key, value)
else if (value is Int) settings.put(key, value)
else if (value is Float) settings.put(key, value.toDouble())
else if (value is Long) settings.put(key, value)
else if (value is Double) settings.put(key, value)
}
json.put("settings", settings)
json.put("magic", "SHIFTRING_BACKUP_V3")
json.put("timestamp", System.currentTimeMillis())
val finalString = json.toString()
val encodedBytes = android.util.Base64.encode(finalString.toByteArray(), android.util.Base64.DEFAULT)
context.contentResolver.openOutputStream(uri)?.use { os ->
os.write(encodedBytes)
}
}
suspend fun restoreData(context: Context, uri: Uri, dao: ShiftDao) = withContext(Dispatchers.IO) {
val bytes = context.contentResolver.openInputStream(uri)?.use {
it.readBytes()
} ?: throw Exception("Failed to read file")
var content = ""
try {
// Try Base64 first (V3)
val decodedBytes = android.util.Base64.decode(bytes, android.util.Base64.DEFAULT)
content = String(decodedBytes)
} catch (e: Exception) {
// Fallback to plain text (V1/V2)
content = String(bytes)
}
val json = JSONObject(content)
val magic = json.optString("magic", "")
if (magic != "SHIFTRING_BACKUP_V1" && magic != "SHIFTRING_BACKUP_V2" && magic != "SHIFTRING_BACKUP_V3") {
throw Exception("올바르지 않은 백업 파일 형식입니다.")
}
// 1. Restore Settings FIRST
if (json.has("settings")) {
val settings = json.getJSONObject("settings")
val prefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE).edit()
prefs.clear()
val keys = settings.keys()
while(keys.hasNext()) {
val key = keys.next()
if (settings.isNull(key)) continue
val value = settings.get(key)
when(value) {
is Boolean -> prefs.putBoolean(key, value)
is Int -> prefs.putInt(key, value)
is String -> prefs.putString(key, value)
is Double -> prefs.putFloat(key, value.toFloat())
is Long -> prefs.putLong(key, value)
}
}
prefs.apply()
}
val restoredPrefs = context.getSharedPreferences("ShiftAlarmPrefs", Context.MODE_PRIVATE)
val fallbackFactory = restoredPrefs.getString("selected_factory", "Jeonju") ?: "Jeonju"
val fallbackTeam = restoredPrefs.getString("selected_team", "A") ?: "A"
// 2. Restore Overrides
if (json.has("overrides")) {
dao.clearOverrides()
val arr = json.getJSONArray("overrides")
for (i in 0 until arr.length()) {
val obj = arr.getJSONObject(i)
dao.insertOverride(ShiftOverride(
factory = obj.optString("factory", fallbackFactory),
team = obj.optString("team", fallbackTeam),
date = obj.getString("date"),
shift = obj.getString("shift")
))
}
}
// 2.5 Restore Custom Alarms
if (json.has("custom_alarms_v2")) {
dao.clearCustomAlarms()
val arr = json.getJSONArray("custom_alarms_v2")
for (i in 0 until arr.length()) {
val obj = arr.getJSONObject(i)
dao.insertCustomAlarm(CustomAlarm(
time = obj.getString("time"),
shiftType = obj.getString("shiftType"),
isEnabled = obj.optBoolean("isEnabled", true),
soundUri = obj.optString("soundUri", null),
snoozeInterval = obj.optInt("snoozeInterval", 5),
snoozeRepeat = obj.optInt("snoozeRepeat", 3)
))
}
}
// 3. Restore Memos
if (json.has("memos")) {
dao.clearMemos()
val arr = json.getJSONArray("memos")
for (i in 0 until arr.length()) {
val obj = arr.getJSONObject(i)
dao.insertMemo(DailyMemo(
obj.getString("date"),
obj.getString("content")
))
}
}
}
}