Skip to content

full_sync

Full Media Library Resync Module.

This module performs a complete resynchronization of the track_list.csv database with the current state of your Music.app library, ensuring complete bidirectional synchronization.

Usage

python -m src.application.full_sync

Or from project root

python -c "import asyncio; from app.full_sync import main; asyncio.run(main())"

run_full_resync async

run_full_resync(
    console_logger,
    error_logger,
    config,
    cache_service,
    track_processor,
)

Run complete media library resynchronization.

This function performs a full sync of the track_list.csv with the current state of the Music.app library, ensuring complete bidirectional synchronization.

Parameters:

Name Type Description Default
console_logger Logger

Logger for console output

required
error_logger Logger

Logger for error output

required
config AppConfig

Typed application configuration

required
cache_service CacheOrchestrator

Cache service instance

required
track_processor TrackProcessor

Track processor instance

required
Source code in src/app/full_sync.py
async def run_full_resync(
    console_logger: logging.Logger,
    error_logger: logging.Logger,
    config: AppConfig,
    cache_service: CacheOrchestrator,
    track_processor: TrackProcessor,
) -> None:
    """Run complete media library resynchronization.

    This function performs a full sync of the track_list.csv with the current state
    of the Music.app library, ensuring complete bidirectional synchronization.

    Args:
        console_logger: Logger for console output
        error_logger: Logger for error output
        config: Typed application configuration
        cache_service: Cache service instance

        track_processor: Track processor instance

    """
    console_logger.info("Starting full media library resync...")

    try:
        # Check if Music.app is running
        if not is_music_app_running(error_logger):
            error_logger.error("Music.app is not running - cannot perform full_sync operation. Please start Music.app before running this script.")
            return

        console_logger.info("Music.app is running")

        # Fetch ALL current tracks from Music.app
        console_logger.info("Fetching all tracks from Music.app...")
        all_tracks = await track_processor.fetch_tracks_async()

        if not all_tracks:
            console_logger.warning("No tracks found in Music.app (full library fetch)")
            return

        console_logger.info("Found %d tracks in Music.app", len(all_tracks))

        # Perform full synchronization
        csv_path = get_full_log_path(config, "csv_output_file", "csv/track_list.csv")

        # Ensure the directory for the CSV file exists
        Path(csv_path).parent.mkdir(parents=True, exist_ok=True)

        console_logger.info("Synchronizing with database: %s", csv_path)

        await sync_track_list_with_current(
            all_tracks,
            csv_path,
            cast(CacheServiceProtocol, cache_service),
            console_logger,
            error_logger,
            partial_sync=False,
            applescript_client=track_processor.ap_client,
        )

        console_logger.info("✅ Full resync completed: %d tracks", len(all_tracks))

    except (OSError, RuntimeError, ValueError) as e:
        error_logger.exception("❌ Full resync failed: %s", e)
        raise

main async

main()

Perform full media library resynchronization.

Source code in src/app/full_sync.py
async def main() -> None:
    """Perform full media library resynchronization."""
    print("Music Genre Updater - Full Media Library Resync")
    print("=" * 55)

    # Determine config path - look for it in the current project directory
    # User config takes precedence over template (my-config.yaml is gitignored)
    config_files = ["my-config.yaml", "config.yaml"]
    if found_configs := [project_root / name for name in config_files if (project_root / name).exists()]:
        config_path = found_configs[0]
        if len(found_configs) > 1:
            print(
                f"Both '{config_files[0]}' and '{config_files[1]}' found.\n"
                f"    Using '{config_path.name}' (higher precedence).\n"
                "    Remove the unused config file to avoid ambiguity."
            )

    else:
        # If neither exists, use default naming
        config_path = project_root / "config.yaml"
    if not config_path.exists():
        print(f"Configuration file not found: {config_path}")
        print("Please make sure you're running this script from the correct directory.")
        sys.exit(1)

    print(f"Using config: {config_path.name}")

    # Load configuration to setup proper loggers
    config = load_config(str(config_path))

    # Setup loggers using centralized system
    console_logger, error_logger, analytics_logger, db_verify_logger, listener = get_loggers(config)

    # Initialize variables for cleanup
    deps = None
    try:
        # Initialize dependency container with proper config path and loggers
        console_logger.info("Initializing services...")
        deps = DependencyContainer(
            config_path=str(config_path),
            console_logger=console_logger,
            error_logger=error_logger,
            analytics_logger=analytics_logger,
            db_verify_logger=db_verify_logger,
        )
        await deps.initialize()

        console_logger.info("Services initialized")

        # Create MusicUpdater instance to get track processor
        music_updater = MusicUpdater(deps)

        # Run the full resync
        console_logger.info("Starting full media library resync...")
        await run_full_resync(console_logger, error_logger, deps.app_config, deps.cache_service, music_updater.track_processor)

        console_logger.info("Full resync completed")
        console_logger.info("track_list.csv is now synchronized with Music.app")

    except KeyboardInterrupt:
        print("\nResync cancelled by user")
        sys.exit(1)
    except (OSError, ValueError, RuntimeError) as e:
        print(f"❌ Resync failed: {e}")
        traceback.print_exc()
        sys.exit(1)
    finally:
        # Cleanup async resources
        if deps:
            await deps.close()

        # Cleanup logging resources
        if listener:
            try:
                listener.stop()
            except (OSError, RuntimeError) as e:
                print(f"Exception during listener.stop(): {e}")
                traceback.print_exc()