...
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
package bums.lunatic.utils.gdrive
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("bums.lunatic.utils.gdrive", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<manifest package="kr.bums.lunatic.utils.gdrive"/>
|
||||
@@ -0,0 +1,76 @@
|
||||
package kr.gdrive.bums.lunatic.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
import com.google.api.client.http.ByteArrayContent
|
||||
import com.google.api.client.http.javanet.NetHttpTransport
|
||||
import com.google.api.client.json.gson.GsonFactory
|
||||
import com.google.api.services.drive.Drive
|
||||
import com.google.api.services.drive.DriveScopes
|
||||
import com.google.api.services.drive.model.File
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class GDriveBackupTask(
|
||||
private val context: Context,
|
||||
private val account: GoogleSignInAccount
|
||||
) {
|
||||
// 💡 백업 실행 (IO 스레드에서 돌아야 함)
|
||||
suspend fun executeBackup(payload: BackupPayload): Result<String> = withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val credential = GoogleAccountCredential.usingOAuth2(context, listOf(DriveScopes.DRIVE_APPDATA))
|
||||
credential.selectedAccount = account.account
|
||||
|
||||
val driveService = Drive.Builder(
|
||||
NetHttpTransport(), GsonFactory.getDefaultInstance(), credential
|
||||
).setApplicationName("LunaticLauncherBackup").build()
|
||||
|
||||
// 1. 매니페스트 업데이트
|
||||
uploadFile(driveService, "appDataFolder", "manifest.json", payload.manifestJson)
|
||||
|
||||
// 2. 폴더 가져오기 or 생성
|
||||
val folderId = getOrCreateFolder(driveService, payload.folderName)
|
||||
|
||||
// 3. 파일들 업로드
|
||||
payload.files.forEach { (fileName, jsonContent) ->
|
||||
uploadFile(driveService, folderId, fileName, jsonContent)
|
||||
}
|
||||
|
||||
Result.success("백업이 완료되었습니다. (${payload.folderName})")
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrCreateFolder(driveService: Drive, folderName: String): String {
|
||||
val query = "mimeType='application/vnd.google-apps.folder' and name='$folderName' and 'appDataFolder' in parents and trashed=false"
|
||||
val fileList = driveService.files().list().setSpaces("appDataFolder").setQ(query).execute()
|
||||
|
||||
if (fileList.files.isNotEmpty()) return fileList.files[0].id
|
||||
|
||||
val folderMetadata = File().apply {
|
||||
name = folderName
|
||||
mimeType = "application/vnd.google-apps.folder"
|
||||
parents = listOf("appDataFolder")
|
||||
}
|
||||
return driveService.files().create(folderMetadata).setFields("id").execute().id
|
||||
}
|
||||
|
||||
private fun uploadFile(driveService: Drive, parentId: String, fileName: String, contentStr: String) {
|
||||
val content = ByteArrayContent.fromString("application/json", contentStr)
|
||||
val query = "name='$fileName' and '$parentId' in parents and trashed=false"
|
||||
val fileList = driveService.files().list().setSpaces("appDataFolder").setQ(query).execute()
|
||||
|
||||
if (fileList.files.isNotEmpty()) {
|
||||
driveService.files().update(fileList.files[0].id, null, content).execute()
|
||||
} else {
|
||||
val fileMetadata = File().apply {
|
||||
name = fileName
|
||||
parents = listOf(parentId)
|
||||
}
|
||||
driveService.files().create(fileMetadata, content).execute()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package kr.gdrive.bums.lunatic.utils
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignIn
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
|
||||
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
|
||||
import com.google.android.gms.common.api.Scope
|
||||
import com.google.api.services.drive.DriveScopes
|
||||
|
||||
class GDriveLoginManager(
|
||||
private val activity: ComponentActivity,
|
||||
private val onLoginResult: (Boolean, GoogleSignInAccount?, String?) -> Unit
|
||||
) {
|
||||
// 필수 권한: 숨겨진 App Data 폴더 접근 권한
|
||||
private val driveScope = Scope(DriveScopes.DRIVE_APPDATA)
|
||||
|
||||
private val signInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
|
||||
.requestEmail()
|
||||
.requestScopes(driveScope)
|
||||
.build()
|
||||
|
||||
private val signInClient = GoogleSignIn.getClient(activity, signInOptions)
|
||||
|
||||
// 액티비티 생성 시점에 등록되어야 하는 런처
|
||||
private val signInLauncher: ActivityResultLauncher<Intent> =
|
||||
activity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val task = GoogleSignIn.getSignedInAccountFromIntent(result.data)
|
||||
try {
|
||||
val account = task.getResult(Exception::class.java)
|
||||
if (account != null && GoogleSignIn.hasPermissions(account, driveScope)) {
|
||||
onLoginResult(true, account, null)
|
||||
} else {
|
||||
onLoginResult(false, null, "드라이브 접근 권한이 부족합니다.")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
onLoginResult(false, null, "로그인 실패: ${e.message}")
|
||||
}
|
||||
} else {
|
||||
onLoginResult(false, null, "로그인이 취소되었습니다.")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 이미 로그인된 유효한 계정이 있는지 확인 (백그라운드에서 유용함)
|
||||
*/
|
||||
fun getSignedInAccount(context: Context = activity): GoogleSignInAccount? {
|
||||
val account = GoogleSignIn.getLastSignedInAccount(context)
|
||||
return if (account != null && GoogleSignIn.hasPermissions(account, driveScope)) {
|
||||
account
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그인 화면 띄우기
|
||||
*/
|
||||
fun signIn() {
|
||||
val account = getSignedInAccount()
|
||||
if (account != null) {
|
||||
onLoginResult(true, account, null)
|
||||
} else {
|
||||
signInLauncher.launch(signInClient.signInIntent)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 로그아웃 (연결 해제)
|
||||
*/
|
||||
fun signOut(onComplete: () -> Unit) {
|
||||
signInClient.signOut().addOnCompleteListener { onComplete() }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package kr.gdrive.bums.lunatic.utils
|
||||
|
||||
sealed class GDriveState {
|
||||
object Idle : GDriveState()
|
||||
object StartingLogin : GDriveState()
|
||||
object Uploading : GDriveState()
|
||||
class Success(val message: String) : GDriveState()
|
||||
class Error(val message: String, val exception: Exception? = null) : GDriveState()
|
||||
}
|
||||
|
||||
// 메인 앱에서 백업할 데이터를 담아 보낼 데이터 클래스
|
||||
data class BackupPayload(
|
||||
val manifestJson: String, // 최상단에 저장될 버전 정보
|
||||
val folderName: String, // 예: "2026-03-05" (날짜 폴더명)
|
||||
val files: Map<String, String> // 파일명(키)과 JSON 텍스트(값)의 쌍
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
package bums.lunatic.utils.gdrive
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user