diff --git a/api/discord_bot.py b/api/discord_bot.py index 15ba38c..66ef30c 100644 --- a/api/discord_bot.py +++ b/api/discord_bot.py @@ -128,7 +128,7 @@ async def on_ready(): orphans = ws_manager.cleanup_orphans(all_channel_ids) if orphans: names = ", ".join(ws.name for ws in orphans) - logger.info(f"유령 워크스페이스 {len(orphans)}개 정리: {names}") + logger.info(f"유령 워크스페이스 {len(orphans)}개 보존(이름 변경): {names}") # 등록된 워크스페이스 표시 ws_list = ws_manager.list_all() diff --git a/core/workspace.py b/core/workspace.py index 8353294..d16fada 100644 --- a/core/workspace.py +++ b/core/workspace.py @@ -160,21 +160,48 @@ class WorkspaceManager: ] def cleanup_orphans(self, valid_channel_ids: set[int]) -> list[Workspace]: - """존재하지 않는 채널의 워크스페이스 정리. + """존재하지 않는 채널의 워크스페이스를 이름 변경하여 보존. - Returns: 제거된 워크스페이스 목록 (알림용) + 삭제하지 않고 이름에 _orphan_날짜 접미사를 붙여 보존합니다. + 기존 설정(Git, Vikunja)은 그대로 유지됩니다. + + Returns: 이름 변경된 워크스페이스 목록 (알림용) """ - orphans = [] + from datetime import datetime + + renamed = [] + suffix = f"_orphan_{datetime.now().strftime('%Y%m%d')}" + for ch_id, ws in list(self.workspaces.items()): if ch_id not in valid_channel_ids: - orphans.append(ws) - del self.workspaces[ch_id] - logger.info(f"유령 워크스페이스 제거: {ws.name} (채널 {ch_id} 없음)") + old_name = ws.name + # 이미 orphan 접미사가 있으면 스킵 + if "_orphan_" in ws.name: + continue + ws.name = f"{ws.name}{suffix}" + ws.channel_id = 0 # 채널 바인딩 해제 + renamed.append(ws) + logger.info( + f"유령 워크스페이스 보존: {old_name} -> {ws.name} " + f"(채널 {ch_id} 없음, 설정 유지)" + ) - if orphans: + if renamed: + # 유령 항목은 channel_id=0 으로 이동 + to_remove = [ + ch_id for ch_id in self.workspaces + if ch_id not in valid_channel_ids and "_orphan_" not in self.workspaces[ch_id].name + ] + # 이미 이름 변경됐으므로 키만 정리 + for ch_id in list(self.workspaces.keys()): + if self.workspaces[ch_id].channel_id == 0 and ch_id != 0: + ws = self.workspaces.pop(ch_id) + # orphan은 이름을 키로 저장 (음수 해시) + orphan_key = -abs(hash(ws.name)) % (10**10) + self.workspaces[orphan_key] = ws self._save() - return orphans + return renamed def set_git(self, channel_id: int, url: str, token: str, repo: str = "", branch: str = "main") -> bool: