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") )) } } } }