def sync_both_differ(
    db: Connection,
    counter: Counter[DataMismatchCounterEnum],
    *,
    include_filesystem_uris: bool = False,
):
    stdout.print(
        "Synchronising differences between existing entries in both databases.", style="notice"
    )

    results = db.execute(load_both_differ_query)
    for result in results:
        track_uri = result["track_uri"]
        parsed_track_uri = urlparse(track_uri)
        if not parsed_track_uri.scheme:
            stderr.print(
                f"Invalid track URI found in internal corrections database: {track_uri}",
                style="warning",
            )
            continue
        elif parsed_track_uri.scheme in ("file", "local"):
            if not include_filesystem_uris:
                continue

        table = make_differ_table(result)
        stdout.print(table)

        response = questionary.select(
            message="",
            choices=(choice_choose_internal, choice_choose_external, choice_manual_edit),
        ).ask()
        if response is None:
            raise AbortCommand

        if response == DataMismatchChoiceEnum.CHOOSE_INTERNAL:
            update_ext_from_main(db, result)
        elif response == DataMismatchChoiceEnum.CHOOSE_EXTERNAL:
            update_main_from_ext(db, result)
        elif response == DataMismatchChoiceEnum.MANUAL_EDIT:
            while True:
                title, artist, album = edit_values(result)
                if questionary.confirm("Empty album correct?", auto_enter=False).ask():
                    break
            update_both(db, track_uri, title, artist, album)
        elif response == DataMismatchChoiceEnum.SKIP_TRACK:
            counter.incr(DataMismatchCounterEnum.SKIP)
            continue
        else:
            stderr.print("Unrecognised option.", style="error")
            raise AbortCommand

        counter.incr(DataMismatchCounterEnum.UPDATE)
def connect_internal_db(config) -> Connection:
    db_path = get_db_path(config)
    if not db_path.is_file():
        stderr.print("Internal database does not exist.", style="error")
        raise AbortCommand

    stdout.print("Connecting to internal database.", style="notice")
    db: Connection = sqlite3.connect(
        db_path,
        timeout=config["advanced_scrobbler"]["db_timeout"],
        factory=Connection,
    )

    internal_user_version = db.execute(
        "PRAGMA user_version").fetchone()["user_version"]
    if internal_user_version != SCHEMA_VERSION:
        stderr.print(
            f"Internal database schema is out of date. Latest version is v{SCHEMA_VERSION}.",
            style="error",
        )
        raise AbortCommand

    stdout.print("Connected to internal database.", style="info")
    return db
def _connect_external_db(db_path: Path, config) -> Connection:
    if not db_path.is_file():
        stderr.print("Specified external database file does not exist.",
                     style="error")
        raise AbortCommand

    stdout.print("Connecting to external database.", style="notice")
    db: Connection = sqlite3.connect(
        db_path,
        timeout=config["advanced_scrobbler"]["db_timeout"],
        factory=Connection,
    )

    external_user_version = db.execute(
        "PRAGMA user_version").fetchone()["user_version"]
    if external_user_version != SCHEMA_VERSION:
        stderr.print(
            f"External database schema is out of date. Latest version is v{SCHEMA_VERSION}.",
            style="error",
        )
        db.close()
        raise AbortCommand

    return db
def run(args: Namespace, config):
    verify_external_db(args.external_db, config)
    db = connect_internal_db(config)
    attach_external_db(db, args.external_db)

    differ_counter: Counter[DataMismatchCounterEnum] = Counter()
    try:
        sync_both_differ(db, differ_counter, include_filesystem_uris=args.include_filesystem_uris)
    finally:
        updated_count = differ_counter.get(DataMismatchCounterEnum.UPDATE)
        if updated_count > 0:
            stdout.print(f"Updated {updated_count} entries!", style="success")
        else:
            stdout.print("No entries were updated.", style="notice")
        skipped_count = differ_counter.get(DataMismatchCounterEnum.SKIP)
        if skipped_count > 0:
            stdout.print(f"Skipped {skipped_count} entries.", style="notice")

    sync_only_in_main(db, include_filesystem_uris=args.include_filesystem_uris)
    sync_only_in_ext(db, include_filesystem_uris=args.include_filesystem_uris)

    stdout.print("Success!", style="success")
    return 0
def sync_only_in_ext(db: Connection, *, include_filesystem_uris: bool = False) -> int:
    stdout.print("Copying entries only in external database to internal database.", style="notice")
    insert_query = "INSERT INTO main.corrections (track_uri, title, artist, album)"
    query = f"{insert_query} {load_only_in_ext_query}"
    if not include_filesystem_uris:
        query += (
            " AND ext.corrections.track_uri NOT LIKE 'file:%'"
            " AND ext.corrections.track_uri NOT LIKE 'local:%'"
        )
    cursor = db.execute(query)

    entry_count = cursor.rowcount
    if entry_count > 0:
        stdout.print(f"Copied {entry_count} entries!", style="success")
    else:
        stdout.print("No entries were copied.", style="notice")

    return entry_count
def attach_external_db(db: Connection, db_path: Path):
    db.execute("ATTACH DATABASE ? as ext", (str(db_path), ))
    stdout.print("Connected to external database.", style="info")
def verify_external_db(db_path: Path, config):
    _connect_external_db(db_path, config)
    stdout.print("Verified external database.", style="info")
def connect_external_db(db_path: Path, config) -> Connection:
    db = _connect_external_db(db_path, config)
    stdout.print("Connected to external database.", style="info")
    return db