package cmdimport ("context""database/sql""fmt""io""os""github.com/navidrome/navidrome/db""github.com/spf13/cobra")func init() {rootCmd.AddCommand(doctorCmd)}var doctorCmd = &cobra.Command{Use: "doctor",Short: "Check your Navidrome installation for problems",Long: "Run read-only health checks and report what was found. Checks the database for " +"corruption and foreign key violations, and reports whether 'navidrome search rebuild' " +"can fix what it finds. This command never alters your data",Run: func(cmd *cobra.Command, _ []string) {runDoctor(cmd.Context())},}func runDoctor(ctx context.Context) {requireExistingDB()healthy := doctor(ctx, db.Db(), os.Stdout)db.Close(ctx)if !healthy {os.Exit(1)}}const recoveryAdvice = "Restore a backup (navidrome backup restore), or try SQLite's '.recover' command."func printFindings(out io.Writer, check, noun string, items []string) {fmt.Fprintf(out, "%s reported %d %s:\n", check, len(items), noun)for _, item := range items {fmt.Fprintln(out, " "+item)}}func doctor(ctx context.Context, database *sql.DB, out io.Writer) bool {healthy := truefmt.Fprintln(out, "Checking database integrity...")issues, truncated, err := db.IntegrityCheck(ctx, database)switch {case err != nil:fmt.Fprintln(out, "The integrity check could not complete: "+err.Error())fmt.Fprintln(out, recoveryAdvice)return falsecase len(issues) == 0:fmt.Fprintln(out, "Integrity check passed.")default:healthy = falseprintFindings(out, "Integrity check", "issue(s)", issues)switch {case truncated:fmt.Fprintln(out, "The integrity check stopped at its limit, so the damage may reach further than listed.")fmt.Fprintln(out, recoveryAdvice)case db.IsFTSCorruptionOnly(issues):fmt.Fprintln(out, "Corruption is limited to the search index. Run 'navidrome search rebuild' to fix it.")default:fmt.Fprintln(out, "Corruption is not limited to the search index, and cannot be repaired automatically.")fmt.Fprintln(out, recoveryAdvice)}}fmt.Fprintln(out, "Checking foreign keys...")violations, err := db.ForeignKeyCheck(ctx, database)switch {case err != nil:healthy = falsefmt.Fprintln(out, "The foreign key check could not complete: "+err.Error())case len(violations) == 0:fmt.Fprintln(out, "Foreign key check passed.")default:healthy = falselines := make([]string, 0, len(violations))for _, v := range violations {lines = append(lines,fmt.Sprintf("%s: %d row(s) reference missing rows in %s", v.Table, v.Count, v.Parent))}printFindings(out, "Foreign key check", "violation(s)", lines)fmt.Fprintln(out, "These are orphaned rows, not corruption. 'navidrome scan -f' clears some of them "+"in library data; the rest have to be removed by hand.")}if healthy {fmt.Fprintln(out, "Database is healthy.")}return healthy}