docs: library comments say what the scanner and resolver do, not how they came to be (#6277)
release / govulncheck (push) Successful in 20s
release / web (push) Successful in 1m20s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m15s
release / android (push) Successful in 5m26s
release / Build signed APK (releases and dev) (push) Successful in 5m45s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
release / govulncheck (push) Successful in 20s
release / web (push) Successful in 1m20s
release / go (push) Successful in 1m37s
release / integration (push) Successful in 5m15s
release / android (push) Successful in 5m26s
release / Build signed APK (releases and dev) (push) Successful in 5m45s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 16s
release / Verify release artifacts (tag releases only) (push) Skipped
Rewrites the internal/library comment blocks to rule 32. Task, milestone, decision and rule ids are gone; the incidents behind them are restated as the constraint they left (why a file is deleted before its row, why the missing mark is a mark, why duplicates block on duration). Counts and album names from one library are dropped from production comments; test comments keep the names their fixtures use. The tagReadVersion log keeps each version's before and after, without the measurements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -22,14 +22,13 @@ type trackReconciler interface {
|
||||
InsertLibraryChanges(ctx context.Context, arg dbq.InsertLibraryChangesParams) error
|
||||
}
|
||||
|
||||
// Reconcile marks tracks whose files have disappeared (#2523).
|
||||
// Reconcile marks tracks whose files have disappeared.
|
||||
//
|
||||
// Why this exists: nothing in Minstrel used to notice a deleted file. The walk
|
||||
// only visits paths that exist, so a row whose file is gone was never scanned,
|
||||
// never errored, never counted — permanently invisible. The watcher ignores
|
||||
// removals by design (see classifyEvent), and the safety-net scan is the same
|
||||
// walk, so it covers additions only. Rows accumulated forever, kept being
|
||||
// offered to recommendations, and failed at playback.
|
||||
// The walk only visits paths that exist, so without this a row whose file is
|
||||
// gone would never be scanned, never error, never be counted. The watcher
|
||||
// ignores removals by design (see classifyEvent), and the safety-net scan is
|
||||
// the same walk, so it covers additions only. Unmarked, such rows would keep
|
||||
// being offered to recommendations and fail at playback.
|
||||
//
|
||||
// Why it MARKS rather than deletes: a missing file is a claim about the
|
||||
// filesystem, and the filesystem lies transiently — an unmounted volume, a
|
||||
@@ -62,9 +61,9 @@ func (s *Scanner) reconcileMissing(
|
||||
if err := s.verifyRootsPresent(); err != nil {
|
||||
return err
|
||||
}
|
||||
// Roots resolved but the walk found nothing. Either the library is genuinely
|
||||
// empty — in which case there is nothing to reconcile — or the mount is
|
||||
// hollow. Both mean: don't act.
|
||||
// Roots resolved but the walk found nothing. Either the library is
|
||||
// genuinely empty — in which case there is nothing to reconcile — or the
|
||||
// mount is hollow. Both mean: don't act.
|
||||
if len(seen) == 0 {
|
||||
return errors.New("walk saw no audio files; refusing to reconcile")
|
||||
}
|
||||
@@ -123,9 +122,8 @@ func (s *Scanner) reconcileMissing(
|
||||
return fmt.Errorf("mark tracks missing: %w", err)
|
||||
}
|
||||
stats.Missing = int(n)
|
||||
// Warn, not Info: every one of these is a library entry the operator
|
||||
// probably didn't intend to lose, and the only place it surfaces today is
|
||||
// this line.
|
||||
// Warn, not Info: every one of these is a library entry an admin probably
|
||||
// didn't intend to lose.
|
||||
s.logger.Warn("library scan: tracks marked missing (files not found)",
|
||||
"count", n, "library_total", len(rows))
|
||||
// Admins are told after the scan has adopted moves and folded
|
||||
@@ -135,13 +133,13 @@ func (s *Scanner) reconcileMissing(
|
||||
}
|
||||
|
||||
// logTrackChanges tells the delta sync that these tracks changed, so clients
|
||||
// pick up the missing mark (#2704).
|
||||
// pick up the missing mark.
|
||||
//
|
||||
// Without this the mark was invisible to every client: MarkTracksMissing and
|
||||
// ClearTracksMissing are plain UPDATEs, and /api/library/sync is a change-log
|
||||
// feed — a row that never produces a change is never re-sent, so a client
|
||||
// would keep its stale copy until an unrelated edit touched the track or the
|
||||
// cursor fell out of the retention window.
|
||||
// Without this the mark would be invisible to every client: MarkTracksMissing
|
||||
// and ClearTracksMissing are plain UPDATEs, and /api/library/sync is a
|
||||
// change-log feed — a row that never produces a change is never re-sent, so a
|
||||
// client would keep its stale copy until an unrelated edit touched the track or
|
||||
// the cursor fell out of the retention window.
|
||||
//
|
||||
// Logged BEFORE the mutation, deliberately, which is the opposite of the
|
||||
// scanner's log-after-success pattern. The two failure modes are not
|
||||
@@ -161,7 +159,7 @@ func logTrackChanges(ctx context.Context, q trackReconciler, ids []pgtype.UUID)
|
||||
}
|
||||
// OpUpsert, not OpDelete: the track still exists and keeps its history —
|
||||
// only its playability changed. A delete would tell clients to drop the
|
||||
// row, which is the behaviour #2704 deliberately rejected.
|
||||
// row, and a missing file is expected to come back.
|
||||
return q.InsertLibraryChanges(ctx, dbq.InsertLibraryChangesParams{
|
||||
EntityType: string(syncpkg.EntityTrack),
|
||||
EntityIds: strIDs,
|
||||
@@ -169,11 +167,11 @@ func logTrackChanges(ctx context.Context, q trackReconciler, ids []pgtype.UUID)
|
||||
})
|
||||
}
|
||||
|
||||
// verifyRootsPresent is the first and most important guard. If a configured root
|
||||
// doesn't resolve to a readable directory, the walk beneath it found nothing and
|
||||
// every row under it would look deleted. An unmounted media volume is the
|
||||
// obvious case, and it is common enough — a container restart racing its volume
|
||||
// mount does exactly this.
|
||||
// verifyRootsPresent is the first and most important guard. If a configured
|
||||
// root doesn't resolve to a readable directory, the walk beneath it found
|
||||
// nothing and every row under it would look deleted. An unmounted media volume
|
||||
// is the obvious case, and it is common enough — a container restart racing its
|
||||
// volume mount does exactly this.
|
||||
func (s *Scanner) verifyRootsPresent() error {
|
||||
roots := s.Roots()
|
||||
if len(roots) == 0 {
|
||||
|
||||
Reference in New Issue
Block a user