Metadata-Version: 2.4
Name: syncfm-core
Version: 0.1.0
Summary: Reusable synchronization engine and CLI for Last.fm, Libre.fm and GNU.fm networks.
Keywords: lastfm,librefm,gnufm,scrobble,music
Author: Hirad
Author-email: Hirad <hirad@hirad.it>
License-Expression: GPL-3.0-or-later
License-File: LICENSE
Classifier: Development Status :: 4 - Beta
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Requires-Dist: keyring>=25.7.0
Requires-Dist: platformdirs>=4.11.1
Requires-Dist: pylast>=7.1.0
Requires-Dist: rich>=15.0.0
Requires-Dist: socksio>=1.0.0
Requires-Dist: tomlkit>=0.15.1
Requires-Dist: typer>=0.27.1
Requires-Python: >=3.12
Project-URL: Homepage, https://git.hirad.it/Hirad/syncfm-core
Project-URL: Documentation, https://git.hirad.it/Hirad/syncfm-core/wiki
Project-URL: Repository, https://git.hirad.it/Hirad/syncfm-core
Project-URL: Issues, https://git.hirad.it/Hirad/syncfm-core/issues
Description-Content-Type: text/markdown

# syncfm-core

`syncfm-core` synchronizes missing scrobbles between Last.fm, Libre.fm, and
GNU.fm-compatible networks. It includes both a reusable, typed Python
application layer and the `syncfm` command-line interface.

The project is currently beta software. Keep a copy of important listening
history before using it with an account you cannot easily restore.

## Features

- Directional synchronization from a source profile to a target profile
- Last.fm, Libre.fm, and custom GNU.fm-compatible instances
- Optional start and end timestamps for bounded synchronization
- Durable SQLite sessions that can be inspected, resumed, or discarded
- Metadata enrichment and retries for transient network failures
- Reusable TOML profiles with credentials in the system keyring by default
- HTTP, HTTPS, SOCKS5, and SOCKS5h proxy support per profile
- Interactive terminal progress dashboard with a plain-text fallback
- Strictly typed public Python models, protocols, services, and exceptions

## Requirements

- Python 3.12 or newer
- API credentials for every configured network
- A working system keyring when using the default credential storage
- [`uv`](https://docs.astral.sh/uv/) for the development workflow shown below

## Installation

Install the released command directly from the Forgejo repository with `uv`:

```console
uv tool install "syncfm-core @ git+https://git.hirad.it/Hirad/syncfm-core.git@0.1.0"
syncfm --help
```

To run the current development version from a checkout:

```console
git clone https://git.hirad.it/Hirad/syncfm-core.git
cd syncfm-core
uv sync
uv run syncfm --help
```

In the examples below, prefix `syncfm` with `uv run` when working from a
checkout instead of an installed tool.

## Quick start

### 1. Create network profiles

Create one profile for each account. Profile names must start with a lowercase
letter or number and may contain lowercase letters, numbers, underscores, and
hyphens.

```console
syncfm profile add lastfm-main \
  --type lastfm \
  --username alice \
  --api-key YOUR_LASTFM_API_KEY
```

The command securely prompts for the API secret and account password. Create a
Libre.fm profile in the same way:

```console
syncfm profile add librefm-main \
  --type librefm \
  --username alice \
  --api-key YOUR_LIBREFM_API_KEY
```

GNU.fm profiles also require the instance hostname:

```console
syncfm profile add community \
  --type gnufm \
  --hostname music.example.org \
  --username alice \
  --api-key YOUR_API_KEY
```

Use `--use-session-key` to enter an existing authenticated session key instead
of a password. Existing profiles are protected from accidental replacement;
pass `--force` to replace one intentionally.

Review stored profiles without exposing their credentials:

```console
syncfm profile list
syncfm profile show lastfm-main
```

### 2. Synchronize scrobbles

Copy scrobbles that exist on the source but are missing from the target:

```console
syncfm sync --source lastfm-main --target librefm-main
```

Synchronization is directional. Reversing `--source` and `--target` performs a
different operation.

By default, the start timestamp is inferred from the target's latest scrobble
and the end timestamp is the current time. If the target has no scrobbles, you
must provide `--start`. Both boundaries are inclusive Unix timestamps:

```console
syncfm sync \
  --source lastfm-main \
  --target librefm-main \
  --start 1704067200 \
  --end 1735689599
```

Before submitting anything, SyncFM fetches source and target snapshots,
compares them, and enriches missing items with available source metadata.

### 3. Recover interrupted work

Sync state is persisted in SQLite. A new sync will not start while unfinished
work exists.

```console
syncfm status
syncfm resume SESSION_ID
```

The original profile names are stored with new sessions, so only the session ID
is normally needed to resume. To abandon an unfinished session and delete its
stored state:

```console
syncfm discard SESSION_ID
```

Both `discard` and `profile remove` ask for confirmation. Use `--yes` only in
non-interactive workflows where the target is already known.

## Profiles, credentials, and proxies

Profiles are stored in a versioned TOML file. Sensitive values use the system
keyring by default:

```console
syncfm profile add my-profile \
  --type lastfm \
  --username alice \
  --api-key YOUR_API_KEY \
  --secret-storage keyring
```

`--secret-storage file` stores the API secret and password or session key
directly in the TOML configuration file. This is useful in carefully protected
headless environments, but the values are plaintext; restrict access to that
file and do not commit it.

Configure a proxy independently for either profile:

```console
syncfm profile add proxied-account \
  --type lastfm \
  --username alice \
  --api-key YOUR_API_KEY \
  --proxy socks5h://127.0.0.1:1080
```

Supported proxy schemes are `http`, `https`, `socks5`, and `socks5h`. A value
without a scheme, such as `127.0.0.1:1080`, defaults to `socks5`.

## Paths and environment variables

SyncFM uses platform-appropriate user directories. Run `syncfm --help` to see
the resolved defaults on the current system. Override them with global options:

```console
syncfm \
  --config /path/to/config.toml \
  --database /path/to/syncfm.sqlite3 \
  sync --source source-profile --target target-profile
```

The CLI recognizes these environment variables:

| Variable | Purpose |
| --- | --- |
| `SYNCFM_CONFIG_PATH` | TOML profile configuration path |
| `SYNCFM_DATABASE_PATH` | SQLite synchronization-state path |
| `SYNCFM_SOURCE_PROFILE` | Default source profile for `sync` or legacy `resume` |
| `SYNCFM_TARGET_PROFILE` | Default target profile for `sync` or legacy `resume` |
| `SYNCFM_PROFILE_API_SECRET` | API secret used by `profile add` |
| `SYNCFM_PROFILE_PASSWORD` | Password used by `profile add` |
| `SYNCFM_PROFILE_SESSION_KEY` | Session key used with `--use-session-key` |

Secret environment variables avoid interactive prompts but may be visible to
other processes or retained in shell and automation configuration. Use the
secret mechanism provided by your operating system or CI platform.

Pass `--verbose` (or `-v`) before the subcommand to enable diagnostic logging:

```console
syncfm --verbose status
```

## Python API

The CLI is a frontend for the same public application services available to
Python callers:

```python
from pathlib import Path

from syncfm_core import (
    NetworkFactory,
    SyncRequest,
    create_profile_service,
    create_sync_service,
)

profiles = create_profile_service()
factory = NetworkFactory()

source = factory.create(profiles.resolve("lastfm-main"))
target = factory.create(profiles.resolve("librefm-main"))

service = create_sync_service(Path("syncfm.sqlite3"))
result = service.sync(
    source,
    target,
    SyncRequest(start_timestamp=1704067200),
    source_profile="lastfm-main",
    target_profile="librefm-main",
)

print(f"Synchronized {result.synced_count} scrobbles")
print(f"Failed: {result.failed_count}")
```

`create_sync_service` accepts an optional `RetryPolicy` and progress callback.
The package also exports its domain models, network protocols, profile service,
and structured exception hierarchy for custom frontends and integrations.

## Command reference

```text
syncfm sync       Start a synchronization
syncfm resume     Resume an unfinished session
syncfm status     List unfinished sessions
syncfm discard    Delete an unfinished session

syncfm profile add
syncfm profile list
syncfm profile show
syncfm profile remove
```

Use `syncfm COMMAND --help` for all arguments and options.

## Development

Create the environment and run the project checks:

```console
uv sync
uv run ruff check .
uv run pyright
uv build
```

Please report bugs and feature requests in the
[issue tracker](https://git.hirad.it/Hirad/syncfm-core/issues). Additional
documentation is available in the
[project wiki](https://git.hirad.it/Hirad/syncfm-core/wiki).

## License

`syncfm-core` is licensed under the
[GNU General Public License v3.0 or later](LICENSE).
