From 17a9f57d59837ab3904974fbc9bb68668ba93ee5 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 8 Jan 2026 01:25:19 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20Notion=20=EB=B0=B0=EA=B2=BD=20=EB=8F=99?= =?UTF-8?q?=EA=B8=B0=ED=99=94=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B0=B0=EA=B2=BD=20=EC=94=AC=20=EB=A1=9C=EB=8D=94=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Notion 동기화 기능 추가: - NotionSyncSettings.cs: Notion API 설정 ScriptableObject - NotionBackgroundSync.cs: Notion API 연동 및 동기화 윈도우 - 배경 씬 정보를 Notion 데이터베이스에 자동 동기화 - Git Raw URL을 통한 썸네일 이미지 연동 - Git 커밋 상태 확인 및 경고 표시 - 배경 씬 로더 버그 수정: - 리컴파일 후 배경 씬 중복 로드 문제 해결 - OnFocus 콜백으로 상태 동기화 강화 - 중복 씬 자동 감지 및 언로드 - 썸네일 캡처 개선: - 기본 해상도 1920x1080 (16:9) - 에디터에서 1:1 중앙 크롭 표시 - 캡처 후 자동 Notion 동기화 옵션 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../Settings/NotionSyncSettings.asset | 3 + .../Settings/NotionSyncSettings.asset.meta | 8 + .../Background/BackgroundSceneLoader.cs | 140 ++- .../Editor/BackgroundSceneLoaderWindow.cs | 185 +++- .../Editor/BackgroundThumbnailCapture.cs | 53 +- .../Background/Editor/NotionBackgroundSync.cs | 803 ++++++++++++++++++ .../Editor/NotionBackgroundSync.cs.meta | 2 + .../Background/Editor/NotionSyncSettings.cs | 76 ++ .../Editor/NotionSyncSettings.cs.meta | 2 + 9 files changed, 1234 insertions(+), 38 deletions(-) create mode 100644 Assets/Resources/Settings/NotionSyncSettings.asset create mode 100644 Assets/Resources/Settings/NotionSyncSettings.asset.meta create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs.meta create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs create mode 100644 Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs.meta diff --git a/Assets/Resources/Settings/NotionSyncSettings.asset b/Assets/Resources/Settings/NotionSyncSettings.asset new file mode 100644 index 00000000..e31d14e5 --- /dev/null +++ b/Assets/Resources/Settings/NotionSyncSettings.asset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:18c621c74aaad475f8f0305201cf98e497be47cf7ea066c1e7ce288260168b35 +size 751 diff --git a/Assets/Resources/Settings/NotionSyncSettings.asset.meta b/Assets/Resources/Settings/NotionSyncSettings.asset.meta new file mode 100644 index 00000000..83de1ff2 --- /dev/null +++ b/Assets/Resources/Settings/NotionSyncSettings.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a1b2c3d4e5f6789012345678abcdef01 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs index 29a20c0e..1d68f452 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs +++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/BackgroundSceneLoader.cs @@ -88,6 +88,102 @@ namespace Streamingle.Background } } + private void Start() + { + // 시작 시 현재 상태 동기화 + SyncCurrentSceneStatus(); + } + + /// + /// 현재 로드된 배경 씬 상태 동기화 (리컴파일/재시작 후 복원용) + /// + public void SyncCurrentSceneStatus() + { + if (sceneDatabase == null) return; + + var loadedBackgroundScenes = new List(); + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (!scene.isLoaded) continue; + + var sceneInfo = sceneDatabase.FindByPath(scene.path); + if (sceneInfo != null) + { + loadedBackgroundScenes.Add(scene); + } + } + + // 중복 배경 씬 처리 (첫 번째만 유지) + if (loadedBackgroundScenes.Count > 1) + { + UnityEngine.Debug.LogWarning($"[Runtime] 중복 배경 씬 감지: {loadedBackgroundScenes.Count}개. 첫 번째만 유지합니다."); + + for (int i = 1; i < loadedBackgroundScenes.Count; i++) + { + var duplicateScene = loadedBackgroundScenes[i]; + UnityEngine.Debug.Log($"[Runtime] 중복 씬 언로드: {duplicateScene.name}"); + SceneManager.UnloadSceneAsync(duplicateScene); + } + } + + // 상태 업데이트 + if (loadedBackgroundScenes.Count > 0) + { + _currentBackgroundScene = loadedBackgroundScenes[0]; + UnityEngine.Debug.Log($"[Runtime] 배경 씬 상태 동기화됨: {_currentBackgroundScene.Value.name}"); + } + else + { + _currentBackgroundScene = null; + } + } + + /// + /// 특정 씬이 이미 로드되어 있는지 확인 + /// + private bool IsSceneAlreadyLoaded(string scenePath) + { + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene.path == scenePath && scene.isLoaded) + { + return true; + } + } + return false; + } + + /// + /// 현재 로드된 모든 배경 씬 언로드 + /// + private void UnloadAllBackgroundScenesImmediate() + { + if (sceneDatabase == null) return; + + var scenesToUnload = new List(); + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (!scene.isLoaded) continue; + + var sceneInfo = sceneDatabase.FindByPath(scene.path); + if (sceneInfo != null) + { + scenesToUnload.Add(scene); + } + } + + foreach (var scene in scenesToUnload) + { + UnityEngine.Debug.Log($"[Runtime] 기존 배경 씬 언로드: {scene.name}"); + SceneManager.UnloadSceneAsync(scene); + } + } + /// /// 현재 활성 씬의 Directional Light와 NiloToonOverrider 백업 및 비활성화 /// @@ -232,25 +328,53 @@ namespace Streamingle.Background yield break; } + // 1. 같은 씬이 이미 로드되어 있는지 확인 + if (IsSceneAlreadyLoaded(sceneInfo.scenePath)) + { + UnityEngine.Debug.Log($"[Runtime] 이미 로드된 씬입니다: {sceneInfo.sceneName}"); + _currentBackgroundScene = SceneManager.GetSceneByPath(sceneInfo.scenePath); + OnSceneLoadCompleted?.Invoke(sceneInfo); + yield break; + } + _isLoading = true; OnSceneLoadStarted?.Invoke(sceneInfo); - // 현재 씬 언로드 (새 배경 로드 시에는 라이팅 복원하지 않음) - if (unloadCurrent && _currentBackgroundScene.HasValue && _currentBackgroundScene.Value.isLoaded) + // 2. 기존 모든 배경 씬 언로드 (새 배경 로드 시에는 라이팅 복원하지 않음) + if (unloadCurrent) { - var unloadOp = SceneManager.UnloadSceneAsync(_currentBackgroundScene.Value); - if (unloadOp != null) + // 모든 배경 씬 찾아서 언로드 + var scenesToUnload = new List(); + for (int i = 0; i < SceneManager.sceneCount; i++) { - while (!unloadOp.isDone) + var scene = SceneManager.GetSceneAt(i); + if (!scene.isLoaded) continue; + + var existingSceneInfo = sceneDatabase?.FindByPath(scene.path); + if (existingSceneInfo != null) { - yield return null; + scenesToUnload.Add(scene); } } - OnSceneUnloaded?.Invoke(_currentBackgroundScene.Value.name); + + foreach (var scene in scenesToUnload) + { + UnityEngine.Debug.Log($"[Runtime] 기존 배경 씬 언로드: {scene.name}"); + var unloadOp = SceneManager.UnloadSceneAsync(scene); + if (unloadOp != null) + { + while (!unloadOp.isDone) + { + yield return null; + } + } + OnSceneUnloaded?.Invoke(scene.name); + } + _currentBackgroundScene = null; } - // 새 씬 로드 + // 3. 새 씬 로드 #if UNITY_EDITOR // 에디터에서는 EditorSceneManager 사용 (별도 처리 필요) if (!Application.isPlaying) diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs index 8553fd9e..ff4debfb 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs +++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundSceneLoaderWindow.cs @@ -306,6 +306,16 @@ namespace Streamingle.Background.Editor EditorApplication.playModeStateChanged += OnPlayModeStateChanged; UnityEditor.SceneManagement.EditorSceneManager.sceneOpened += OnSceneOpened; UnityEditor.SceneManagement.EditorSceneManager.sceneClosed += OnSceneClosed; + + // 리컴파일 후에도 현재 상태 복원 + UpdateCurrentSceneStatus(); + } + + private void OnFocus() + { + // 윈도우가 포커스될 때마다 상태 동기화 + UpdateCurrentSceneStatus(); + Repaint(); } private void OnDisable() @@ -485,6 +495,33 @@ namespace Streamingle.Background.Editor _thumbnailCache.Clear(); } + /// + /// 16:9 (또는 다른 비율) 이미지를 1:1로 중앙 크롭하여 표시 + /// + private void DrawCroppedThumbnail(Rect displayRect, Texture2D texture) + { + if (texture == null) return; + + float textureAspect = (float)texture.width / texture.height; + + // 정사각형이거나 세로가 더 긴 경우 기존 방식 + if (textureAspect <= 1.0f) + { + GUI.DrawTexture(displayRect, texture, ScaleMode.ScaleToFit); + return; + } + + // 16:9 등 가로가 더 긴 경우: 중앙에서 정사각형으로 크롭 + // UV 좌표 계산: 세로는 전체, 가로는 중앙 부분만 + float cropWidth = (float)texture.height / texture.width; // 정사각형 비율 + float uvOffsetX = (1f - cropWidth) / 2f; // 중앙 정렬 + + // texCoords: (x, y, width, height) - UV 공간에서의 영역 + Rect texCoords = new Rect(uvOffsetX, 0f, cropWidth, 1f); + + GUI.DrawTextureWithTexCoords(displayRect, texture, texCoords); + } + /// /// 특정 썸네일 캐시 무효화 및 새로고침 /// @@ -869,7 +906,8 @@ namespace Streamingle.Background.Editor if (thumbnail != null) { - GUI.DrawTexture(thumbnailRect, thumbnail, ScaleMode.ScaleToFit); + // 16:9 이미지를 1:1로 중앙 크롭하여 표시 + DrawCroppedThumbnail(thumbnailRect, thumbnail); } else { @@ -934,7 +972,8 @@ namespace Streamingle.Background.Editor if (thumbnail != null) { - GUI.DrawTexture(thumbRect, thumbnail, ScaleMode.ScaleToFit); + // 16:9 이미지를 1:1로 중앙 크롭하여 표시 + DrawCroppedThumbnail(thumbRect, thumbnail); } else { @@ -1032,17 +1071,23 @@ namespace Streamingle.Background.Editor private void LoadSceneEditor(BackgroundSceneInfo sceneInfo, LoadSceneMode mode) { - // 기존 배경 씬 언로드 (Additive 모드일 때) - if (mode == LoadSceneMode.Additive && _loadedBackgroundScene.HasValue) + // 1. 같은 씬이 이미 로드되어 있는지 확인 + if (IsSceneAlreadyLoaded(sceneInfo.scenePath)) { - if (_loadedBackgroundScene.Value.isLoaded) - { - // 자동 복원 처리 (새 배경 로드 시에는 복원하지 않음 - 새 배경 라이팅이 덮어씀) - EditorSceneManager.CloseScene(_loadedBackgroundScene.Value, true); - } + UnityEngine.Debug.Log($"[BackgroundSceneLoader] 이미 로드된 씬입니다: {sceneInfo.sceneName}"); + var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath); + _loadedBackgroundScene = existingScene; + _currentSceneName = sceneInfo.sceneName; + return; } - // 씬 로드 + // 2. Additive 모드일 때 기존 모든 배경 씬 언로드 + if (mode == LoadSceneMode.Additive) + { + UnloadAllBackgroundScenes(); + } + + // 3. 씬 로드 var openMode = mode == LoadSceneMode.Additive ? OpenSceneMode.Additive : OpenSceneMode.Single; @@ -1082,13 +1127,23 @@ namespace Streamingle.Background.Editor private void LoadSceneRuntime(BackgroundSceneInfo sceneInfo, LoadSceneMode mode) { - // 기존 배경 씬 언로드 - if (mode == LoadSceneMode.Additive && _loadedBackgroundScene.HasValue && _loadedBackgroundScene.Value.isLoaded) + // 1. 같은 씬이 이미 로드되어 있는지 확인 + if (IsSceneAlreadyLoaded(sceneInfo.scenePath)) { - SceneManager.UnloadSceneAsync(_loadedBackgroundScene.Value); + UnityEngine.Debug.Log($"[BackgroundSceneLoader] [PlayMode] 이미 로드된 씬입니다: {sceneInfo.sceneName}"); + var existingScene = SceneManager.GetSceneByPath(sceneInfo.scenePath); + _loadedBackgroundScene = existingScene; + _currentSceneName = sceneInfo.sceneName; + return; } - // 라이팅 백업 및 비활성화 + // 2. Additive 모드일 때 기존 모든 배경 씬 언로드 + if (mode == LoadSceneMode.Additive) + { + UnloadAllBackgroundScenes(); + } + + // 3. 라이팅 백업 및 비활성화 if (mode == LoadSceneMode.Additive && _copyLighting) { var activeScene = SceneManager.GetActiveScene(); @@ -1163,21 +1218,109 @@ namespace Streamingle.Background.Editor private void UpdateCurrentSceneStatus() { - // 현재 로드된 배경 씬 확인 + if (_database == null) return; + + // 현재 로드된 모든 배경 씬 찾기 + var loadedBackgroundScenes = new List(); + for (int i = 0; i < SceneManager.sceneCount; i++) { var scene = SceneManager.GetSceneAt(i); - var sceneInfo = _database?.FindByPath(scene.path); + if (!scene.isLoaded) continue; + + var sceneInfo = _database.FindByPath(scene.path); if (sceneInfo != null) { - _loadedBackgroundScene = scene; - _currentSceneName = sceneInfo.sceneName; - return; + loadedBackgroundScenes.Add(scene); } } - _loadedBackgroundScene = null; - _currentSceneName = "없음"; + // 배경 씬이 여러 개 로드된 경우 경고 (첫 번째만 유지) + if (loadedBackgroundScenes.Count > 1) + { + UnityEngine.Debug.LogWarning($"[BackgroundSceneLoader] 중복 배경 씬 감지: {loadedBackgroundScenes.Count}개. 첫 번째만 유지합니다."); + + // 첫 번째를 제외하고 모두 언로드 + for (int i = 1; i < loadedBackgroundScenes.Count; i++) + { + var duplicateScene = loadedBackgroundScenes[i]; + UnityEngine.Debug.Log($"[BackgroundSceneLoader] 중복 씬 언로드: {duplicateScene.name}"); + + if (Application.isPlaying) + { + SceneManager.UnloadSceneAsync(duplicateScene); + } + else + { + EditorSceneManager.CloseScene(duplicateScene, true); + } + } + } + + // 상태 업데이트 + if (loadedBackgroundScenes.Count > 0) + { + var firstScene = loadedBackgroundScenes[0]; + _loadedBackgroundScene = firstScene; + _currentSceneName = _database.FindByPath(firstScene.path)?.sceneName ?? firstScene.name; + } + else + { + _loadedBackgroundScene = null; + _currentSceneName = "없음"; + } + } + + /// + /// 현재 로드된 모든 배경 씬 언로드 + /// + private void UnloadAllBackgroundScenes() + { + if (_database == null) return; + + var scenesToUnload = new List(); + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (!scene.isLoaded) continue; + + var sceneInfo = _database.FindByPath(scene.path); + if (sceneInfo != null) + { + scenesToUnload.Add(scene); + } + } + + foreach (var scene in scenesToUnload) + { + UnityEngine.Debug.Log($"[BackgroundSceneLoader] 기존 배경 씬 언로드: {scene.name}"); + + if (Application.isPlaying) + { + SceneManager.UnloadSceneAsync(scene); + } + else + { + EditorSceneManager.CloseScene(scene, true); + } + } + } + + /// + /// 특정 씬이 이미 로드되어 있는지 확인 + /// + private bool IsSceneAlreadyLoaded(string scenePath) + { + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene.path == scenePath && scene.isLoaded) + { + return true; + } + } + return false; } private void SelectInProject(BackgroundSceneInfo sceneInfo) diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs index d2e47efe..5dd1474d 100644 --- a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs +++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/BackgroundThumbnailCapture.cs @@ -10,12 +10,15 @@ namespace Streamingle.Background.Editor /// public class BackgroundThumbnailCapture : EditorWindow { - private const int DEFAULT_WIDTH = 512; - private const int DEFAULT_HEIGHT = 512; + // 16:9 기본값 (Notion용) + private const int DEFAULT_WIDTH = 1920; + private const int DEFAULT_HEIGHT = 1080; private Camera _selectedCamera; private int _width = DEFAULT_WIDTH; private int _height = DEFAULT_HEIGHT; + + private NotionSyncSettings _notionSettings; private string _savePath; private string _fileName; private bool _useSceneCamera = true; @@ -39,6 +42,17 @@ namespace Streamingle.Background.Editor _database = AssetDatabase.LoadAssetAtPath( "Assets/Resources/Settings/BackgroundSceneDatabase.asset"); + // Notion 설정 로드 + _notionSettings = AssetDatabase.LoadAssetAtPath( + "Assets/Resources/Settings/NotionSyncSettings.asset"); + + // 설정에서 해상도 가져오기 + if (_notionSettings != null) + { + _width = _notionSettings.thumbnailWidth; + _height = _notionSettings.thumbnailHeight; + } + // 현재 로드된 배경 씬 찾기 FindCurrentBackgroundScene(); @@ -319,15 +333,12 @@ namespace Streamingle.Background.Editor EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("256x256")) { _width = 256; _height = 256; } - if (GUILayout.Button("512x512")) { _width = 512; _height = 512; } - if (GUILayout.Button("1024x1024")) { _width = 1024; _height = 1024; } + if (GUILayout.Button("1920x1080 (권장)")) { _width = 1920; _height = 1080; } + if (GUILayout.Button("1280x720")) { _width = 1280; _height = 720; } + if (GUILayout.Button("960x540")) { _width = 960; _height = 540; } EditorGUILayout.EndHorizontal(); - EditorGUILayout.BeginHorizontal(); - if (GUILayout.Button("16:9")) { _width = 1920; _height = 1080; } - if (GUILayout.Button("4:3")) { _width = 1024; _height = 768; } - EditorGUILayout.EndHorizontal(); + EditorGUILayout.HelpBox("16:9 비율 썸네일이 권장됩니다.\n에디터에서는 1:1로 크롭되어 표시되고, Notion에는 16:9 원본이 업로드됩니다.", MessageType.Info); EditorGUILayout.Space(10); @@ -569,9 +580,33 @@ namespace Streamingle.Background.Editor loaderWindow.RefreshThumbnail(assetPath); } + // Notion 자동 동기화 + if (_notionSettings != null && _notionSettings.autoSyncOnCapture && _currentSceneInfo != null) + { + SyncToNotionAsync(_currentSceneInfo); + } + EditorUtility.DisplayDialog("완료", $"썸네일이 저장되었습니다.\n{fullPath}", "확인"); } + private async void SyncToNotionAsync(BackgroundSceneInfo sceneInfo) + { + if (_notionSettings == null || !_notionSettings.IsValid()) + { + UnityEngine.Debug.LogWarning("[Notion] 설정이 올바르지 않아 동기화를 건너뜁니다."); + return; + } + + var sync = new NotionBackgroundSync(_notionSettings, _database); + + sync.OnProgress += (msg) => UnityEngine.Debug.Log($"[Notion] {msg}"); + sync.OnError += (msg) => UnityEngine.Debug.LogError($"[Notion] {msg}"); + sync.OnCompleted += () => UnityEngine.Debug.Log("[Notion] 동기화 완료"); + + await sync.SyncSingleToNotion(sceneInfo); + sync.Dispose(); + } + private void CaptureAllBackgroundScenes() { if (_database == null) diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs new file mode 100644 index 00000000..4e708add --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs @@ -0,0 +1,803 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using UnityEditor; +using UnityEngine; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Streamingle.Background.Editor +{ + /// + /// Notion 배경 씬 동기화 매니저 + /// + public class NotionBackgroundSync + { + private const string NOTION_API_VERSION = "2022-06-28"; + private const string NOTION_API_URL = "https://api.notion.com/v1"; + + private readonly NotionSyncSettings _settings; + private readonly BackgroundSceneDatabase _database; + private readonly HttpClient _httpClient; + + public event Action OnProgress; + public event Action OnError; + public event Action OnCompleted; + + public NotionBackgroundSync(NotionSyncSettings settings, BackgroundSceneDatabase database) + { + _settings = settings; + _database = database; + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Add("Authorization", $"Bearer {_settings.notionApiToken}"); + _httpClient.DefaultRequestHeaders.Add("Notion-Version", NOTION_API_VERSION); + } + + /// + /// 모든 배경 씬을 Notion에 동기화 + /// + public async Task SyncAllToNotion() + { + if (!_settings.IsValid()) + { + OnError?.Invoke("Notion 설정이 올바르지 않습니다. 설정을 확인하세요."); + return; + } + + try + { + OnProgress?.Invoke("데이터베이스 속성 확인 중..."); + + // 0. 데이터베이스 속성 확인 및 생성 + await EnsureDatabaseProperties(); + + OnProgress?.Invoke("기존 Notion 항목 조회 중..."); + + // 1. 기존 Notion 데이터베이스 항목 조회 + var existingPages = await GetExistingPages(); + + int total = _database.scenes.Count; + int current = 0; + + foreach (var sceneInfo in _database.scenes) + { + current++; + OnProgress?.Invoke($"동기화 중: {sceneInfo.sceneName} ({current}/{total})"); + + // 기존 페이지가 있는지 확인 + string existingPageId = FindExistingPage(existingPages, sceneInfo.scenePath); + + if (existingPageId != null) + { + // 업데이트 + await UpdatePage(existingPageId, sceneInfo); + } + else + { + // 새로 생성 + await CreatePage(sceneInfo); + } + + // API Rate Limit 방지 + await Task.Delay(350); + } + + OnProgress?.Invoke("동기화 완료!"); + OnCompleted?.Invoke(); + } + catch (Exception ex) + { + OnError?.Invoke($"동기화 실패: {ex.Message}"); + UnityEngine.Debug.LogException(ex); + } + } + + /// + /// 단일 배경 씬을 Notion에 동기화 + /// + public async Task SyncSingleToNotion(BackgroundSceneInfo sceneInfo) + { + if (!_settings.IsValid()) + { + OnError?.Invoke("Notion 설정이 올바르지 않습니다."); + return; + } + + try + { + OnProgress?.Invoke("데이터베이스 속성 확인 중..."); + await EnsureDatabaseProperties(); + + OnProgress?.Invoke($"동기화 중: {sceneInfo.sceneName}"); + + var existingPages = await GetExistingPages(); + string existingPageId = FindExistingPage(existingPages, sceneInfo.scenePath); + + if (existingPageId != null) + { + await UpdatePage(existingPageId, sceneInfo); + } + else + { + await CreatePage(sceneInfo); + } + + OnProgress?.Invoke("동기화 완료!"); + OnCompleted?.Invoke(); + } + catch (Exception ex) + { + OnError?.Invoke($"동기화 실패: {ex.Message}"); + } + } + + /// + /// 데이터베이스 속성 확인 및 누락된 속성 생성 + /// + private async Task EnsureDatabaseProperties() + { + // 데이터베이스 정보 조회 + var response = await _httpClient.GetAsync($"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}"); + var responseText = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new Exception($"데이터베이스 조회 실패: {responseText}"); + } + + var dbInfo = JObject.Parse(responseText); + var existingProperties = dbInfo["properties"] as JObject; + + // 필요한 속성들 정의 + var requiredProperties = new Dictionary + { + // Title 속성은 이미 존재하므로 이름만 확인 + ["카테고리"] = new JObject { ["select"] = new JObject { ["options"] = new JArray() } }, + ["씬 경로"] = new JObject { ["rich_text"] = new JObject() }, + ["폴더 이름"] = new JObject { ["rich_text"] = new JObject() }, + ["썸네일"] = new JObject { ["url"] = new JObject() }, + ["마지막 동기화"] = new JObject { ["date"] = new JObject() }, + ["마지막 사용"] = new JObject { ["date"] = new JObject() } + }; + + // 누락된 속성 찾기 + var propertiesToAdd = new JObject(); + foreach (var prop in requiredProperties) + { + bool found = false; + foreach (var existing in existingProperties) + { + if (existing.Key == prop.Key) + { + found = true; + break; + } + } + + if (!found) + { + propertiesToAdd[prop.Key] = prop.Value; + UnityEngine.Debug.Log($"[Notion] 속성 추가 예정: {prop.Key}"); + } + } + + // 누락된 속성이 있으면 추가 + if (propertiesToAdd.Count > 0) + { + OnProgress?.Invoke($"누락된 속성 {propertiesToAdd.Count}개 생성 중..."); + + var updateBody = new JObject + { + ["properties"] = propertiesToAdd + }; + + var content = new StringContent(updateBody.ToString(), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}") + { + Content = content + }; + + var updateResponse = await _httpClient.SendAsync(request); + var updateResponseText = await updateResponse.Content.ReadAsStringAsync(); + + if (!updateResponse.IsSuccessStatusCode) + { + UnityEngine.Debug.LogWarning($"[Notion] 속성 추가 실패 (무시하고 계속): {updateResponseText}"); + } + else + { + UnityEngine.Debug.Log($"[Notion] 데이터베이스 속성 {propertiesToAdd.Count}개 추가됨"); + } + } + + // Title 속성 이름 확인 (기본 "이름" 또는 "Name") + _titlePropertyName = FindTitlePropertyName(existingProperties); + UnityEngine.Debug.Log($"[Notion] Title 속성 이름: {_titlePropertyName}"); + } + + private string _titlePropertyName = "이름"; + + /// + /// Title 타입 속성의 이름 찾기 + /// + private string FindTitlePropertyName(JObject properties) + { + foreach (var prop in properties) + { + var propType = prop.Value["type"]?.Value(); + if (propType == "title") + { + return prop.Key; + } + } + return "이름"; // 기본값 + } + + /// + /// 기존 Notion 페이지 목록 조회 + /// + private async Task> GetExistingPages() + { + var pages = new List(); + string cursor = null; + + do + { + var requestBody = new JObject + { + ["page_size"] = 100 + }; + + if (cursor != null) + { + requestBody["start_cursor"] = cursor; + } + + var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync( + $"{NOTION_API_URL}/databases/{_settings.notionDatabaseId}/query", + content); + + var responseText = await response.Content.ReadAsStringAsync(); + + if (!response.IsSuccessStatusCode) + { + throw new Exception($"Notion API 오류: {responseText}"); + } + + var result = JObject.Parse(responseText); + var results = result["results"] as JArray; + + if (results != null) + { + foreach (var page in results) + { + pages.Add(page as JObject); + } + } + + cursor = result["has_more"]?.Value() == true + ? result["next_cursor"]?.Value() + : null; + + } while (cursor != null); + + return pages; + } + + /// + /// 씬 경로로 기존 페이지 찾기 + /// + private string FindExistingPage(List pages, string scenePath) + { + foreach (var page in pages) + { + var properties = page["properties"] as JObject; + if (properties == null) continue; + + // "씬 경로" 또는 "ScenePath" 속성에서 검색 + var pathProperty = properties["씬 경로"] ?? properties["ScenePath"]; + if (pathProperty == null) continue; + + var richText = pathProperty["rich_text"] as JArray; + if (richText != null && richText.Count > 0) + { + var text = richText[0]["plain_text"]?.Value(); + if (text == scenePath) + { + return page["id"]?.Value(); + } + } + } + + return null; + } + + /// + /// 새 페이지 생성 + /// + private async Task CreatePage(BackgroundSceneInfo sceneInfo) + { + var properties = BuildProperties(sceneInfo); + + var requestBody = new JObject + { + ["parent"] = new JObject { ["database_id"] = _settings.notionDatabaseId }, + ["properties"] = properties + }; + + // 썸네일이 있으면 커버 이미지로 추가 + if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath)) + { + // Git 커밋 상태 확인 + bool isCommitted = CheckIfFileIsCommitted(sceneInfo.thumbnailPath); + if (!isCommitted) + { + UnityEngine.Debug.LogWarning($"[Notion] 썸네일이 Git에 커밋되지 않음: {sceneInfo.thumbnailPath}\n" + + "Notion에서 이미지가 표시되지 않을 수 있습니다. Git commit & push 후 다시 동기화하세요."); + } + + string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath); + requestBody["cover"] = new JObject + { + ["type"] = "external", + ["external"] = new JObject { ["url"] = imageUrl } + }; + } + + var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync($"{NOTION_API_URL}/pages", content); + + if (!response.IsSuccessStatusCode) + { + var responseText = await response.Content.ReadAsStringAsync(); + throw new Exception($"페이지 생성 실패: {responseText}"); + } + + UnityEngine.Debug.Log($"[Notion] 페이지 생성됨: {sceneInfo.sceneName}"); + } + + /// + /// 기존 페이지 업데이트 + /// + private async Task UpdatePage(string pageId, BackgroundSceneInfo sceneInfo) + { + var properties = BuildProperties(sceneInfo); + + var requestBody = new JObject + { + ["properties"] = properties + }; + + // 썸네일이 있으면 커버 이미지도 업데이트 + if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath)) + { + // Git 커밋 상태 확인 + bool isCommitted = CheckIfFileIsCommitted(sceneInfo.thumbnailPath); + if (!isCommitted) + { + UnityEngine.Debug.LogWarning($"[Notion] 썸네일이 Git에 커밋되지 않음: {sceneInfo.thumbnailPath}\n" + + "Notion에서 이미지가 표시되지 않을 수 있습니다. Git commit & push 후 다시 동기화하세요."); + } + + string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath); + requestBody["cover"] = new JObject + { + ["type"] = "external", + ["external"] = new JObject { ["url"] = imageUrl } + }; + } + + var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/pages/{pageId}") + { + Content = content + }; + + var response = await _httpClient.SendAsync(request); + + if (!response.IsSuccessStatusCode) + { + var responseText = await response.Content.ReadAsStringAsync(); + throw new Exception($"페이지 업데이트 실패: {responseText}"); + } + + UnityEngine.Debug.Log($"[Notion] 페이지 업데이트됨: {sceneInfo.sceneName}"); + } + + /// + /// Notion 속성 빌드 + /// + private JObject BuildProperties(BackgroundSceneInfo sceneInfo) + { + var properties = new JObject(); + + // 이름 (Title) - 동적으로 찾은 속성 이름 사용 + properties[_titlePropertyName] = new JObject + { + ["title"] = new JArray + { + new JObject + { + ["text"] = new JObject { ["content"] = sceneInfo.sceneName } + } + } + }; + + // 카테고리 (Select) + string category = ExtractCategory(sceneInfo.categoryName); + properties["카테고리"] = new JObject + { + ["select"] = new JObject { ["name"] = category } + }; + + // 씬 경로 (Rich Text) + properties["씬 경로"] = new JObject + { + ["rich_text"] = new JArray + { + new JObject + { + ["text"] = new JObject { ["content"] = sceneInfo.scenePath } + } + } + }; + + // 썸네일 URL (URL) + if (!string.IsNullOrEmpty(sceneInfo.thumbnailPath)) + { + string imageUrl = _settings.GetGitRawUrl(sceneInfo.thumbnailPath); + properties["썸네일"] = new JObject + { + ["url"] = imageUrl + }; + } + + // 폴더 이름 (Rich Text) + properties["폴더 이름"] = new JObject + { + ["rich_text"] = new JArray + { + new JObject + { + ["text"] = new JObject { ["content"] = sceneInfo.categoryName } + } + } + }; + + // 마지막 동기화 (Date) + properties["마지막 동기화"] = new JObject + { + ["date"] = new JObject + { + ["start"] = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss") + } + }; + + return properties; + } + + /// + /// 카테고리 이름에서 태그 부분 추출 (예: "[공용]농구장" → "공용") + /// + private string ExtractCategory(string folderName) + { + if (string.IsNullOrEmpty(folderName)) return "기타"; + + int startBracket = folderName.IndexOf('['); + int endBracket = folderName.IndexOf(']'); + + if (startBracket >= 0 && endBracket > startBracket) + { + return folderName.Substring(startBracket + 1, endBracket - startBracket - 1); + } + + return folderName; + } + + /// + /// 파일이 Git에 커밋되어 있는지 확인 + /// + /// Unity Asset 경로 (예: Assets/ResourcesData/...) + /// 커밋 여부 (true: 커밋됨, false: 커밋 안됨 또는 오류) + private bool CheckIfFileIsCommitted(string assetPath) + { + try + { + // Unity 프로젝트 루트 경로 + string projectPath = Path.GetDirectoryName(Application.dataPath); + string filePath = Path.Combine(projectPath, assetPath).Replace("\\", "/"); + + // 파일이 존재하는지 먼저 확인 + if (!File.Exists(filePath)) + { + return false; + } + + // git ls-files로 파일이 추적되고 있는지 확인 + var lsFilesProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "git", + Arguments = $"ls-files \"{assetPath}\"", + WorkingDirectory = projectPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + StandardOutputEncoding = Encoding.UTF8 + } + }; + + lsFilesProcess.Start(); + string lsFilesOutput = lsFilesProcess.StandardOutput.ReadToEnd().Trim(); + lsFilesProcess.WaitForExit(); + + // 파일이 Git에 추적되고 있지 않으면 + if (string.IsNullOrEmpty(lsFilesOutput)) + { + return false; + } + + // git status로 변경 사항 확인 (커밋되지 않은 변경이 있는지) + var statusProcess = new Process + { + StartInfo = new ProcessStartInfo + { + FileName = "git", + Arguments = $"status --porcelain \"{assetPath}\"", + WorkingDirectory = projectPath, + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + StandardOutputEncoding = Encoding.UTF8 + } + }; + + statusProcess.Start(); + string statusOutput = statusProcess.StandardOutput.ReadToEnd().Trim(); + statusProcess.WaitForExit(); + + // status 출력이 비어있으면 커밋된 상태 (변경 없음) + // 출력이 있으면 modified/added/untracked 등의 상태 + return string.IsNullOrEmpty(statusOutput); + } + catch (Exception ex) + { + UnityEngine.Debug.LogWarning($"[Notion] Git 상태 확인 실패: {ex.Message}"); + return false; + } + } + + /// + /// 사용 이력 기록 (Notion에) + /// + public async Task RecordUsage(BackgroundSceneInfo sceneInfo) + { + if (!_settings.trackUsageHistory || !_settings.IsValid()) return; + + try + { + var existingPages = await GetExistingPages(); + string pageId = FindExistingPage(existingPages, sceneInfo.scenePath); + + if (pageId != null) + { + // "마지막 사용" 날짜 업데이트 + var properties = new JObject + { + ["마지막 사용"] = new JObject + { + ["date"] = new JObject + { + ["start"] = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss") + } + } + }; + + var requestBody = new JObject { ["properties"] = properties }; + var content = new StringContent(requestBody.ToString(), Encoding.UTF8, "application/json"); + var request = new HttpRequestMessage(new HttpMethod("PATCH"), $"{NOTION_API_URL}/pages/{pageId}") + { + Content = content + }; + + await _httpClient.SendAsync(request); + UnityEngine.Debug.Log($"[Notion] 사용 이력 기록됨: {sceneInfo.sceneName}"); + } + } + catch (Exception ex) + { + UnityEngine.Debug.LogWarning($"[Notion] 사용 이력 기록 실패: {ex.Message}"); + } + } + + public void Dispose() + { + _httpClient?.Dispose(); + } + } + + /// + /// Notion 동기화 에디터 윈도우 + /// + public class NotionSyncWindow : EditorWindow + { + private NotionSyncSettings _settings; + private BackgroundSceneDatabase _database; + private string _statusMessage = ""; + private bool _isSyncing; + private float _progress; + + [MenuItem("Streamingle/Notion Background Sync")] + public static void ShowWindow() + { + var window = GetWindow("Notion 동기화"); + window.minSize = new Vector2(400, 350); + window.Show(); + } + + private void OnEnable() + { + LoadSettings(); + } + + private void LoadSettings() + { + _settings = AssetDatabase.LoadAssetAtPath( + "Assets/Resources/Settings/NotionSyncSettings.asset"); + + _database = AssetDatabase.LoadAssetAtPath( + "Assets/Resources/Settings/BackgroundSceneDatabase.asset"); + } + + private void OnGUI() + { + EditorGUILayout.Space(10); + EditorGUILayout.LabelField("Notion 배경 씬 동기화", EditorStyles.boldLabel); + EditorGUILayout.Space(10); + + // 설정 확인 + if (_settings == null) + { + EditorGUILayout.HelpBox("NotionSyncSettings을 찾을 수 없습니다.\n" + + "Assets/Resources/Settings/NotionSyncSettings.asset을 생성하세요.", MessageType.Warning); + + if (GUILayout.Button("설정 파일 생성")) + { + CreateSettingsFile(); + } + return; + } + + // 설정 표시 + EditorGUILayout.BeginVertical(EditorStyles.helpBox); + EditorGUILayout.LabelField("설정", EditorStyles.boldLabel); + + EditorGUI.BeginChangeCheck(); + + _settings.notionApiToken = EditorGUILayout.PasswordField("Notion API Token", _settings.notionApiToken); + _settings.notionDatabaseId = EditorGUILayout.TextField("Database ID", _settings.notionDatabaseId); + + EditorGUILayout.Space(5); + + _settings.gitServerUrl = EditorGUILayout.TextField("Git Server URL", _settings.gitServerUrl); + _settings.gitRepoPath = EditorGUILayout.TextField("Repo Path", _settings.gitRepoPath); + _settings.gitBranch = EditorGUILayout.TextField("Branch", _settings.gitBranch); + + EditorGUILayout.Space(5); + + _settings.autoSyncOnCapture = EditorGUILayout.Toggle("썸네일 캡처 시 자동 동기화", _settings.autoSyncOnCapture); + _settings.trackUsageHistory = EditorGUILayout.Toggle("사용 이력 추적", _settings.trackUsageHistory); + + if (EditorGUI.EndChangeCheck()) + { + EditorUtility.SetDirty(_settings); + AssetDatabase.SaveAssets(); + } + + EditorGUILayout.EndVertical(); + + // 유효성 검사 + EditorGUILayout.Space(10); + + if (!_settings.HasNotionToken) + { + EditorGUILayout.HelpBox("Notion API Token이 필요합니다.\n" + + "https://www.notion.so/my-integrations 에서 발급하세요.", MessageType.Warning); + } + + if (!_settings.HasValidDatabaseId) + { + EditorGUILayout.HelpBox("Notion Database ID가 필요합니다.\n" + + "데이터베이스 URL에서 32자리 ID를 복사하세요.", MessageType.Warning); + } + + // Git URL 미리보기 + EditorGUILayout.Space(10); + EditorGUILayout.LabelField("이미지 URL 예시:", EditorStyles.boldLabel); + string exampleUrl = _settings.GetGitRawUrl("Assets/ResourcesData/Background/Example/Scene/Example.png"); + EditorGUILayout.SelectableLabel(exampleUrl, EditorStyles.miniLabel, GUILayout.Height(20)); + + // 동기화 버튼 + EditorGUILayout.Space(20); + + GUI.enabled = _settings.IsValid() && !_isSyncing && _database != null; + + if (GUILayout.Button("전체 동기화", GUILayout.Height(35))) + { + SyncAll(); + } + + GUI.enabled = true; + + // 상태 메시지 + if (!string.IsNullOrEmpty(_statusMessage)) + { + EditorGUILayout.Space(10); + EditorGUILayout.HelpBox(_statusMessage, MessageType.Info); + } + + // 데이터베이스 정보 + if (_database != null) + { + EditorGUILayout.Space(10); + EditorGUILayout.LabelField($"배경 씬 수: {_database.scenes.Count}개", EditorStyles.miniLabel); + } + } + + private void CreateSettingsFile() + { + string dirPath = "Assets/Resources/Settings"; + if (!Directory.Exists(dirPath)) + { + Directory.CreateDirectory(dirPath); + } + + _settings = ScriptableObject.CreateInstance(); + AssetDatabase.CreateAsset(_settings, $"{dirPath}/NotionSyncSettings.asset"); + AssetDatabase.SaveAssets(); + + UnityEngine.Debug.Log("NotionSyncSettings 파일이 생성되었습니다."); + } + + private async void SyncAll() + { + _isSyncing = true; + _statusMessage = "동기화 시작..."; + Repaint(); + + var sync = new NotionBackgroundSync(_settings, _database); + + sync.OnProgress += (msg) => + { + _statusMessage = msg; + Repaint(); + }; + + sync.OnError += (msg) => + { + _statusMessage = $"오류: {msg}"; + _isSyncing = false; + Repaint(); + }; + + sync.OnCompleted += () => + { + _statusMessage = "동기화 완료!"; + _isSyncing = false; + Repaint(); + }; + + await sync.SyncAllToNotion(); + + sync.Dispose(); + } + } +} diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs.meta new file mode 100644 index 00000000..865884df --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionBackgroundSync.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e384f532f7d5f744083ecba75388fde0 \ No newline at end of file diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs new file mode 100644 index 00000000..c1364ddc --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs @@ -0,0 +1,76 @@ +using UnityEngine; + +namespace Streamingle.Background.Editor +{ + /// + /// Notion 동기화 설정을 저장하는 ScriptableObject + /// + [CreateAssetMenu(fileName = "NotionSyncSettings", menuName = "Streamingle/Notion Sync Settings")] + public class NotionSyncSettings : ScriptableObject + { + [Header("Notion API 설정")] + [Tooltip("Notion Integration Token (ntn_xxx... 또는 secret_xxx...)")] + public string notionApiToken = "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"; + + [Tooltip("Notion Database ID (32자리 hex) - 배경 일람")] + public string notionDatabaseId = "25ea7d46b958805f88fcf127979934bf"; + + [Header("Git 설정")] + [Tooltip("Git 서버 URL (예: https://kindnick-git.duckdns.org)")] + public string gitServerUrl = "https://kindnick-git.duckdns.org"; + + [Tooltip("Git 리포지토리 경로 (예: kindnick/Streamingle_URP)")] + public string gitRepoPath = "kindnick/Streamingle_URP"; + + [Tooltip("Git 브랜치 (예: main)")] + public string gitBranch = "main"; + + [Header("동기화 설정")] + [Tooltip("썸네일 캡처 시 자동 동기화")] + public bool autoSyncOnCapture = false; + + [Tooltip("배경 씬 로드 시 사용 이력 기록")] + public bool trackUsageHistory = true; + + [Header("썸네일 설정")] + [Tooltip("썸네일 너비")] + public int thumbnailWidth = 1920; + + [Tooltip("썸네일 높이 (16:9 비율)")] + public int thumbnailHeight = 1080; + + /// + /// Git Raw 파일 URL 생성 (Gitea/Gogs 형식) + /// + public string GetGitRawUrl(string assetPath) + { + // Assets/ResourcesData/Background/... 형식의 경로를 Git Raw URL로 변환 + // Gitea Raw URL 형식: {serverUrl}/{repoPath}/raw/branch/{branch}/{filePath} + string relativePath = assetPath.Replace("\\", "/"); + + return $"{gitServerUrl}/{gitRepoPath}/raw/branch/{gitBranch}/{relativePath}"; + } + + /// + /// 설정이 유효한지 확인 + /// + public bool IsValid() + { + return !string.IsNullOrEmpty(notionApiToken) && + !string.IsNullOrEmpty(notionDatabaseId) && + !string.IsNullOrEmpty(gitServerUrl) && + !string.IsNullOrEmpty(gitRepoPath); + } + + /// + /// Notion API Token이 설정되었는지 확인 + /// + public bool HasNotionToken => !string.IsNullOrEmpty(notionApiToken) && + (notionApiToken.StartsWith("secret_") || notionApiToken.StartsWith("ntn_")); + + /// + /// Database ID가 유효한 형식인지 확인 + /// + public bool HasValidDatabaseId => !string.IsNullOrEmpty(notionDatabaseId) && notionDatabaseId.Length >= 32; + } +} diff --git a/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs.meta b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs.meta new file mode 100644 index 00000000..c140b1fc --- /dev/null +++ b/Assets/Scripts/Streamingle/StreamingleControl/Background/Editor/NotionSyncSettings.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0d02f18044447044a927d2258edc4286 \ No newline at end of file