v1.0.0 - ShiftRing 정식 첫 릴리즈\
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
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")
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user