diff --git a/README.md b/README.md
index 7b36429..06c3258 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# playlink
+# Playlink
-Deduplicate music libraries with hardlinks. playlink finds songs that exist in both a **master** library (e.g. your full `Albums`) and a
+Deduplicate music libraries with hardlinks. Playlink finds songs that exist in both a **master** library (e.g. your full `Albums`) and a
**slave** library (e.g. a curated `Singles` library) and replaces the slave copy with a hardlink to the master copy. Each song then takes
up disk space only once, while both libraries keep looking exactly the same to Plex, Navidrome, Jellyfin and other media servers.
@@ -13,7 +13,7 @@ Files are matched by their **path relative to each library root**. Any folder la
`Genre/Artist/Title.ext`, flat, …) as long as both libraries use the same one, so that a song sits at the same relative
path on both sides. The fallbacks below catch small differences, and a scan warns when the two layouts don't line up.
-| Situation | What playlink does |
+| Situation | What Playlink does |
|---|---|
| Same relative path, **byte-identical** | Status `identical`. Link with one click, in bulk, or automatically after each scan. |
| Same path, **different bytes** (re-encode, different tags) | Status `different`. Manual review with a codec/bitrate/bit-depth/duration comparison. |
@@ -51,7 +51,7 @@ Safeguards:
codec-weighted bitrate (Opus and AAC count for more per kbps than MP3). You then have three choices:
- **Upgrade / link to master copy.** The default. When the master copy is better, the match is flagged as an **upgrade** and listed
- under the *Upgrades* filter. The master is kept because it belongs to a complete album. If the master is *lower* quality, playlink
+ under the *Upgrades* filter. The master is kept because it belongs to a complete album. If the master is *lower* quality, Playlink
warns you and asks for explicit confirmation.
- **Use slave copy as master.** The override. The slave's better file replaces the master file, the old master file is quarantined, and both
libraries share the better file.
@@ -76,14 +76,14 @@ Each row has the match status and type, a recommendation, both paths, where link
bit depth and duration for both sides, the quality verdict and any notes. The file opens cleanly in Excel, Numbers and LibreOffice
(UTF-8, and cells a spreadsheet would treat as formulas are escaped).
-**Folders without music** (the *Cleanup* tab). Choose the slave or master library and **Find folders**: playlink lists the top-most
+**Folders without music** (the *Cleanup* tab). Choose the slave or master library and **Find folders**: Playlink lists the top-most
folders with no music file anywhere inside them (music = the pair's file extensions), such as album folders left empty after linking or
ones holding only a cover or playlist, with what they still contain. Empty folders are pre-selected, folders that still hold files are
not. **Remove** asks for confirmation (and an extra acknowledgement when the folders still hold files), re-checks each folder for music
right before touching it, and moves it into the quarantine instead of deleting it: it can be restored from History until the retention
period purges it. The library root, the quarantine and hidden folders are never listed, and dry run applies.
-**Backups** (Settings → Backups), similar to Sonarr. A backup is a zip of playlink's database (settings, library pairs, scan results and
+**Backups** (Settings → Backups), similar to Sonarr. A backup is a zip of Playlink's database (settings, library pairs, scan results and
history, not music files) taken safely while the app runs. Choose a folder (default `/config/backups`), a schedule (off, every 1, 3, 7
or 14 days) and a retention period for scheduled backups; **Back up now** makes a manual one (kept until deleted). Backups can be
downloaded, deleted, or restored, also from an uploaded zip. A restore is validated first (damaged backups and ones from a newer version
@@ -92,12 +92,12 @@ policy brings it back automatically. Optionally, **back up before every action i
before scans change files), never one per file; if that backup fails the action does not run, and the newest 10 are kept.
**Build mode** covers the "start fresh" workflow. Point the slave at an empty folder, browse the master library, tick the
-folders or songs you want, and playlink hardlinks them in at the same relative path. **Remove** deletes slave files, but only ones that are hardlinks of the
+folders or songs you want, and Playlink hardlinks them in at the same relative path. **Remove** deletes slave files, but only ones that are hardlinks of the
master file.
## Safety model
-playlink is built so that no user data is ever lost, even if it crashes or loses power mid-operation:
+Playlink is built so that no user data is ever lost, even if it crashes or loses power mid-operation:
- **Dry run is on by default.** Every action is recorded in Activity as *planned* until you switch to live mode.
- **Files are re-verified before any change.** Both files must still be plain regular files with the same inode, size and mtime that
@@ -159,7 +159,7 @@ Run a scan, check the Review tab, and turn off dry run once the planned actions
- **Both libraries must be visible through one bind mount.** Docker bind mounts are separate mount points, and `link(2)` fails across
them (`EXDEV`) even when they are on the same disk. Mount a common parent folder, as shown above.
-- **Both libraries must be on the same filesystem.** playlink checks this when you save a pair and before every operation.
+- **Both libraries must be on the same filesystem.** Playlink checks this when you save a pair and before every operation.
- **Unraid:** hardlinks through `/mnt/user` require *Settings → Global Share Settings → Tunable (support Hard Links)* to be *Yes*
(the default). If the share uses a cache pool, make sure your mover preserves hardlinks. Every link is verified by inode, and a
failed link aborts safely with the original untouched.
@@ -176,7 +176,7 @@ Run a scan, check the Review tab, and turn off dry run once the planned actions
| `PLAYLINK_LOG_LEVEL` | `info` | `debug` for verbose logs. |
**Hide quarantine from Plex** (Settings → Media servers) writes a `.plexignore` into each pair's quarantine folder so Plex does not
-pick up quarantined songs; new quarantine folders get it automatically. Turning it off removes the file again, but only if playlink
+pick up quarantined songs; new quarantine folders get it automatically. Turning it off removes the file again, but only if Playlink
created it.
Per-pair settings (in the UI) are file extensions, auto-link, approximate matching and its threshold, scan interval, quarantine folder,
diff --git a/internal/backup/backup.go b/internal/backup/backup.go
index 1da3674..14cf951 100644
--- a/internal/backup/backup.go
+++ b/internal/backup/backup.go
@@ -41,7 +41,7 @@ const (
var nameRe = regexp.MustCompile(`^playlink_backup_(manual|scheduled|action)_\d{4}\.\d{2}\.\d{2}_\d{2}\.\d{2}\.\d{2}(_\d+)?\.zip$`)
-var ErrBadName = errors.New("not a playlink backup file name")
+var ErrBadName = errors.New("not a Playlink backup file name")
// Settings stored in the settings table.
const (
@@ -341,7 +341,7 @@ func (m *Manager) Stage(ctx context.Context, zipPath string, maxSchema int) erro
}
}
if dbf == nil {
- return errors.New("the zip does not contain a playlink database")
+ return errors.New("the zip does not contain a Playlink database")
}
tmp := filepath.Join(m.dataDir, stagedName+".tmp")
_ = os.Remove(tmp)
@@ -395,14 +395,14 @@ func validate(ctx context.Context, path string, maxSchema int) error {
}
var schema int
if err := db.QueryRowContext(ctx, `SELECT COALESCE(MAX(v), 0) FROM schema_version`).Scan(&schema); err != nil {
- return errors.New("the backup is not a playlink database")
+ return errors.New("the backup is not a Playlink database")
}
if schema < 1 || schema > maxSchema {
- return fmt.Errorf("the backup is from a newer version of playlink (schema %d, this version supports up to %d)", schema, maxSchema)
+ return fmt.Errorf("the backup is from a newer version of Playlink (schema %d, this version supports up to %d)", schema, maxSchema)
}
var n int
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name IN ('pairs', 'users', 'operations')`).Scan(&n); err != nil || n != 3 {
- return errors.New("the backup is not a playlink database")
+ return errors.New("the backup is not a Playlink database")
}
return nil
}
diff --git a/internal/engine/ops.go b/internal/engine/ops.go
index fda0083..cc76f4e 100644
--- a/internal/engine/ops.go
+++ b/internal/engine/ops.go
@@ -626,7 +626,7 @@ func (e *Engine) Undo(ctx context.Context, opID int64) error {
default:
// History can hold operations of features that were removed (tag
// edits, file moves); undoing them as a link would be wrong.
- return fmt.Errorf("%q operations can no longer be undone from playlink; their originals stay in the quarantine folder", o.Action)
+ return fmt.Errorf("%q operations can no longer be undone from Playlink; their originals stay in the quarantine folder", o.Action)
}
l, err := e.loadPair(ctx, o.PairID)
if err != nil {
diff --git a/internal/engine/scan.go b/internal/engine/scan.go
index 4a98a3b..2c6ec15 100644
--- a/internal/engine/scan.go
+++ b/internal/engine/scan.go
@@ -345,7 +345,7 @@ func (e *Engine) scan(ctx context.Context, j *Job, pairID int64, trigger string)
}
if ms.symlinks+ss.symlinks > 0 {
warns = append(warns, store.Warning{Title: fmt.Sprintf("Ignored %d symlinks", ms.symlinks+ss.symlinks),
- Detail: "playlink never follows or replaces symlinks."})
+ Detail: "Playlink never follows or replaces symlinks."})
}
if e.prober.Bin == "" {
warns = append(warns, store.Warning{Title: "ffprobe was not found",
diff --git a/web/index.html b/web/index.html
index db89679..237a62d 100644
--- a/web/index.html
+++ b/web/index.html
@@ -4,7 +4,8 @@
-
Every change playlink makes is recorded here and can be undone.
+
Every change Playlink makes is recorded here and can be undone.
{(["all", "quarantine"] as const).map((v) => (
diff --git a/web/src/pages/Backups.tsx b/web/src/pages/Backups.tsx
index 6c2eec1..b8aec79 100644
--- a/web/src/pages/Backups.tsx
+++ b/web/src/pages/Backups.tsx
@@ -5,7 +5,7 @@ import { api, type Backup, type BackupConfig } from "../api";
import { Alert, Badge, Button, Card, Field, Modal, Spinner, Toggle, formatAgo, formatBytes, inputCls } from "../ui";
import { useToast } from "../toast";
-const typeTone = { manual: "violet", scheduled: "blue", action: "gray" } as const;
+const typeTone = { manual: "accent", scheduled: "blue", action: "gray" } as const;
const typeLabel = { manual: "manual", scheduled: "scheduled", action: "before action" } as const;
export function BackupsCard() {
@@ -75,7 +75,7 @@ export function BackupsCard() {
Backups
- Backups hold playlink's database: settings, library pairs, scan results and history. They do not contain music files; file
+ Backups hold Playlink's database: settings, library pairs, scan results and history. They do not contain music files; file
changes are undone from History.
@@ -175,15 +175,15 @@ export function BackupsCard() {
setRestore(null)} title="Restore this backup?">
- playlink's database is replaced with {restore?.name ?? restore?.file?.name}: settings, library
+ Playlink's database is replaced with {restore?.name ?? restore?.file?.name}: settings, library
pairs, scan results and history go back to how they were then.
-
The backup is checked first; a damaged backup or one from a newer playlink version is refused.
+
The backup is checked first; a damaged backup or one from a newer Playlink version is refused.
The current database is kept next to it (playlink.db.pre-restore-…), so this can be reversed by hand.
Music files are not changed. History entries made after the backup are no longer listed, so undo them first if needed.
- playlink restarts to finish. In Docker it comes back by itself; if you run it directly, start it again.
+ Playlink restarts to finish. In Docker it comes back by itself; if you run it directly, start it again.
@@ -214,7 +214,7 @@ export function BackupsCard() {
);
}
-/** Waits for playlink to come back after a restore, then reloads the page. */
+/** Waits for Playlink to come back after a restore, then reloads the page. */
function Restarting() {
const [slow, setSlow] = useState(false);
useEffect(() => {
@@ -235,12 +235,12 @@ function Restarting() {
return (
{}} title="Restoring backup…">
- playlink is restarting with the restored database. This page reloads when it is back.
+ Playlink is restarting with the restored database. This page reloads when it is back.
{slow && (
- playlink hasn't come back yet. If you run it without Docker (or without a restart policy), start it again; the restore is applied when
+ Playlink hasn't come back yet. If you run it without Docker (or without a restart policy), start it again; the restore is applied when
it starts.
- Build the slave library from the master. Browse the master library, pick folders or songs, and playlink hardlinks them into the slave
+ Build the slave library from the master. Browse the master library, pick folders or songs, and Playlink hardlinks them into the slave
library at the same relative path. No extra space is used. Existing slave files are never
overwritten, and Remove only deletes slave files that are hardlinks of the master file, so the master copy is always kept.
@@ -81,7 +81,7 @@ export function Build({ pair }: { pair: Pair }) {