fix duplicate folders for target server
This commit is contained in:
parent
17d20df80a
commit
fe5e680fa4
5 changed files with 42 additions and 51 deletions
|
|
@ -13,44 +13,17 @@ logger = logging.getLogger(__name__)
|
||||||
class MigrationContext:
|
class MigrationContext:
|
||||||
"""Holds state and connections for reading from Discord and writing to the target platform."""
|
"""Holds state and connections for reading from Discord and writing to the target platform."""
|
||||||
|
|
||||||
def __init__(self, config: AppConfig, target_platform: str | None = None, source_mode: str = "live"):
|
def __init__(self, config: AppConfig, target_platform: str | None = None, source_mode: str = "live", base_dir: str = ""):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.source_mode = source_mode
|
self.source_mode = source_mode
|
||||||
# If caller didn't specify, fall back to config value
|
# If caller didn't specify, fall back to config value
|
||||||
self.target_platform = target_platform or config.target_platform or "fluxer"
|
self.target_platform = target_platform or config.target_platform or "fluxer"
|
||||||
|
self.state = MigrationState()
|
||||||
server_id = config.target_server_id or "unconfigured"
|
|
||||||
fillers = {"000000000000000000", "DISCORD_SERVER_ID", "FLUXER_COMMUNITY_ID",
|
|
||||||
"STOAT_SERVER_ID", "TARGET_SERVER_ID", ""}
|
|
||||||
if server_id in fillers:
|
|
||||||
server_id = "unconfigured"
|
|
||||||
|
|
||||||
# Try to find an existing state folder for this server_id
|
|
||||||
import os
|
|
||||||
|
|
||||||
state_file: str | Path = ""
|
|
||||||
messages_file: str | Path = ""
|
|
||||||
|
|
||||||
if server_id != "unconfigured":
|
|
||||||
logger.info(f"Probing for existing migration folder for Server ID: {server_id}")
|
|
||||||
for d in Path(".").iterdir():
|
|
||||||
if d.is_dir() and d.name.endswith(f"-{server_id}"):
|
|
||||||
logger.info(f"Targeting existing folder: {d.name}")
|
|
||||||
state_file = d / "state-migration.json"
|
|
||||||
messages_file = d / "message-tracker.json"
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
logger.info(f"No existing folder found for {server_id}. A new one will be created upon first state change.")
|
|
||||||
|
|
||||||
self.state = MigrationState(
|
|
||||||
state_file=state_file,
|
|
||||||
messages_file=messages_file
|
|
||||||
)
|
|
||||||
|
|
||||||
# Select the appropriate source reader
|
# Select the appropriate source reader
|
||||||
if source_mode == "backup":
|
if source_mode == "backup":
|
||||||
from src.core.backup_reader import BackupReader
|
from src.core.backup_reader import BackupReader
|
||||||
backup_path = self._find_backup_path(config.discord_server_id)
|
backup_path = self._find_backup_path(config.discord_server_id, base_dir)
|
||||||
self.discord_reader = BackupReader(backup_path)
|
self.discord_reader = BackupReader(backup_path)
|
||||||
logger.info(f"Source mode: BACKUP — reading from {backup_path}")
|
logger.info(f"Source mode: BACKUP — reading from {backup_path}")
|
||||||
else:
|
else:
|
||||||
|
|
@ -76,16 +49,26 @@ class MigrationContext:
|
||||||
|
|
||||||
self.is_running = False
|
self.is_running = False
|
||||||
|
|
||||||
@staticmethod
|
def _find_backup_path(self, server_id: str, base_dir_str: str) -> Path:
|
||||||
def _find_backup_path(server_id: str) -> Path:
|
|
||||||
"""Searches workspace for a DISCORD_BACKUP-{server_id} directory. Creates it if missing."""
|
"""Searches workspace for a DISCORD_BACKUP-{server_id} directory. Creates it if missing."""
|
||||||
for d in Path(".").rglob(f"DISCORD_BACKUP-{server_id}"):
|
base_dir = Path(base_dir_str) if base_dir_str else Path(".")
|
||||||
if d.is_dir():
|
|
||||||
|
# 1. Search inside the specific workspace directory first
|
||||||
|
if base_dir.exists() and base_dir.is_dir():
|
||||||
|
for d in base_dir.iterdir():
|
||||||
|
if d.is_dir() and d.name.endswith(f"-{server_id}") and "DISCORD_BACKUP" in d.name:
|
||||||
logger.info(f"Found backup directory: {d}")
|
logger.info(f"Found backup directory: {d}")
|
||||||
return d
|
return d
|
||||||
|
|
||||||
# If not found, create it in the current directory
|
# 2. Fallback to global search if it wasn't found in the workspace
|
||||||
new_path = Path(".") / f"DISCORD_BACKUP-{server_id}"
|
for d in Path(".").rglob(f"DISCORD_BACKUP-{server_id}"):
|
||||||
|
if d.is_dir():
|
||||||
|
logger.info(f"Found backup directory globally: {d}")
|
||||||
|
return d
|
||||||
|
|
||||||
|
# If not found anywhere, create it inside the workspace
|
||||||
|
base_dir.mkdir(exist_ok=True)
|
||||||
|
new_path = base_dir / f"DISCORD_BACKUP-{server_id}"
|
||||||
logger.info(f"No existing backup directory found for {server_id}. Creating new one: {new_path}")
|
logger.info(f"No existing backup directory found for {server_id}. Creating new one: {new_path}")
|
||||||
new_path.mkdir(exist_ok=True)
|
new_path.mkdir(exist_ok=True)
|
||||||
return new_path
|
return new_path
|
||||||
|
|
|
||||||
|
|
@ -323,15 +323,21 @@ class MigrationState:
|
||||||
new_folder = base / f"{clean_name}-{server_id}"
|
new_folder = base / f"{clean_name}-{server_id}"
|
||||||
logger.info(f"Setting active migration folder: {new_folder}")
|
logger.info(f"Setting active migration folder: {new_folder}")
|
||||||
|
|
||||||
# If we have an existing folder that is different, rename it
|
# 1. Search base_dir to see if an older folder for this server_id exists
|
||||||
if self.state_file and self.state_file.parent.exists() and self.state_file.parent != new_folder:
|
existing_folder: Path | None = None
|
||||||
# Check if it's actually in a server-specific folder (not roots)
|
if base.exists() and base.is_dir():
|
||||||
if self.state_file.parent.name.endswith(f"-{server_id}"):
|
for d in base.iterdir():
|
||||||
logger.info(f"Renaming active folder from {self.state_file.parent.name} to {new_folder.name}")
|
if d.is_dir() and d.name.endswith(f"-{server_id}"):
|
||||||
|
existing_folder = d
|
||||||
|
break
|
||||||
|
|
||||||
|
# 2. Rename it if it doesn't match the new desired name
|
||||||
|
if existing_folder and existing_folder != new_folder:
|
||||||
|
logger.info(f"Renaming existing folder {existing_folder.name} to {new_folder.name}")
|
||||||
try:
|
try:
|
||||||
self.state_file.parent.rename(new_folder)
|
existing_folder.rename(new_folder)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Could not rename {self.state_file.parent} to {new_folder}: {e}")
|
logger.debug(f"Could not rename {existing_folder} to {new_folder}: {e}")
|
||||||
|
|
||||||
new_folder.mkdir(parents=True, exist_ok=True)
|
new_folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -300,7 +300,7 @@ class DiscordExporter:
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def export_channel_messages(self, channel_id: int, progress_callback=None, force=False, accumulated_count=0):
|
async def export_channel_messages(self, channel_id: int, progress_callback=None, force=False, accumulated_count=0, after_id: int | None = None):
|
||||||
"""Fetches and saves message history for a channel, handling incremental sync. Returns the total messages processed."""
|
"""Fetches and saves message history for a channel, handling incremental sync. Returns the total messages processed."""
|
||||||
channel = await self.reader.get_channel(channel_id)
|
channel = await self.reader.get_channel(channel_id)
|
||||||
if not channel:
|
if not channel:
|
||||||
|
|
@ -372,7 +372,9 @@ class DiscordExporter:
|
||||||
last_id = None
|
last_id = None
|
||||||
|
|
||||||
# Load existing messages for incremental sync (skip if force)
|
# Load existing messages for incremental sync (skip if force)
|
||||||
if not force and json_file.exists():
|
if after_id is not None:
|
||||||
|
last_id = after_id
|
||||||
|
elif not force and json_file.exists():
|
||||||
try:
|
try:
|
||||||
with open(json_file, "r", encoding="utf-8") as f:
|
with open(json_file, "r", encoding="utf-8") as f:
|
||||||
old_data = json.load(f)
|
old_data = json.load(f)
|
||||||
|
|
@ -669,7 +671,7 @@ class DiscordExporter:
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def export_threads(self, channel_id: int, progress_callback=None, force=False, accumulated_count=0):
|
async def export_threads(self, channel_id: int, progress_callback=None, force=False, accumulated_count=0, after_id: int | None = None):
|
||||||
"""Exports active and archived threads for a channel. Returns accumulated message count."""
|
"""Exports active and archived threads for a channel. Returns accumulated message count."""
|
||||||
channel = await self.reader.get_channel(channel_id)
|
channel = await self.reader.get_channel(channel_id)
|
||||||
if not hasattr(channel, "threads") and not hasattr(channel, "public_archived_threads"):
|
if not hasattr(channel, "threads") and not hasattr(channel, "public_archived_threads"):
|
||||||
|
|
@ -711,7 +713,7 @@ class DiscordExporter:
|
||||||
await asyncio.sleep(0) # important yield between threads
|
await asyncio.sleep(0) # important yield between threads
|
||||||
|
|
||||||
# First backup the full thread — this creates {thread_id}.json with totalAttachmentSizeBytes
|
# First backup the full thread — this creates {thread_id}.json with totalAttachmentSizeBytes
|
||||||
accumulated_count = await self.export_channel_messages(thread.id, progress_callback=progress_callback, force=force, accumulated_count=accumulated_count)
|
accumulated_count = await self.export_channel_messages(thread.id, progress_callback=progress_callback, force=force, accumulated_count=accumulated_count, after_id=after_id)
|
||||||
thread_count += 1
|
thread_count += 1
|
||||||
|
|
||||||
# Then populate the forum root JSON with the starter message
|
# Then populate the forum root JSON with the starter message
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class BackupPane(Container):
|
||||||
self.cfg_name = cfg_name
|
self.cfg_name = cfg_name
|
||||||
self.config_path = cfg_path
|
self.config_path = cfg_path
|
||||||
self.config = load_config(cfg_path)
|
self.config = load_config(cfg_path)
|
||||||
self.engine = MigrationContext(self.config, target_platform=self.config.target_platform or "fluxer")
|
self.engine = MigrationContext(self.config, target_platform=self.config.target_platform or "fluxer", base_dir=f"Reaper-{self.cfg_name}")
|
||||||
self.exporter = DiscordExporter(self.engine.discord_reader, base_dir=f"Reaper-{self.cfg_name}")
|
self.exporter = DiscordExporter(self.engine.discord_reader, base_dir=f"Reaper-{self.cfg_name}")
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
|
|
@ -74,7 +74,7 @@ class BackupPane(Container):
|
||||||
|
|
||||||
def reload_config(self) -> None:
|
def reload_config(self) -> None:
|
||||||
self.config = load_config(self.config_path)
|
self.config = load_config(self.config_path)
|
||||||
self.engine = MigrationContext(self.config, target_platform=self.config.target_platform or "fluxer")
|
self.engine = MigrationContext(self.config, target_platform=self.config.target_platform or "fluxer", base_dir=f"Reaper-{self.cfg_name}")
|
||||||
self.exporter = DiscordExporter(self.engine.discord_reader, base_dir=f"Reaper-{self.cfg_name}")
|
self.exporter = DiscordExporter(self.engine.discord_reader, base_dir=f"Reaper-{self.cfg_name}")
|
||||||
self._validate()
|
self._validate()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ class ShuttlePane(Container):
|
||||||
|
|
||||||
def _rebuild_engine(self):
|
def _rebuild_engine(self):
|
||||||
source = "backup" if self.config.tool_mode == "backup_transfer" else "live"
|
source = "backup" if self.config.tool_mode == "backup_transfer" else "live"
|
||||||
self.engine = MigrationContext(self.config, self.target_platform, source_mode=source)
|
self.engine = MigrationContext(self.config, self.target_platform, source_mode=source, base_dir=self._base_dir())
|
||||||
|
|
||||||
# ── labels ────────────────────────────────────────────────────────────
|
# ── labels ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue