Skip to content

Feat/missing metadata#168

Merged
contre95 merged 2 commits into
mainfrom
feat/missing_metadata
Jun 12, 2026
Merged

Feat/missing metadata#168
contre95 merged 2 commits into
mainfrom
feat/missing_metadata

Conversation

@contre95

@contre95 contre95 commented Jun 12, 2026

Copy link
Copy Markdown
Owner

PR Improves the importing queue, making it possible to have multiple reasons for why a track was queued in a single queue item. It also expands the missing_metadata queue type and makes it configurable so that the user can decide weather it wants to let tracks to it's library with/withtout Title, Artist, Album, Genre and/or Year.
Defaults for those type have been defined in the domains and added to the configuration feature.

Additionally this PR removes the defaults value for the download job.

Summary by CodeRabbit

Release Notes

  • New Features

    • Import settings now support per-field control of missing metadata handling (Artist, Album, Title, Year, Genre).
    • Queue items can now display multiple status types simultaneously (e.g., Duplicate + Missing Metadata).
  • Improvements

    • Items with missing required metadata are now blocked from import/replace actions (only cancel/delete/skip available).
    • Tag writing improved to prevent stale metadata from persisting in files.
  • Documentation

    • Updated import configuration and queue behavior documentation to reflect granular metadata controls and multi-type queue items.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors the import queue from single-type items to multi-type items, and changes track metadata defaulting and validation from global settings to per-field configuration. Queue items now store a Types slice with helper methods HasType and PrimaryType; download jobs defer metadata processing to import; and all consumers (import service, views, handlers, templates) adapt to use the new model.

Changes

Queue item type model and track metadata configuration

Layer / File(s) Summary
Queue item type model refactoring
src/music/queue.go, src/features/importing/queue.go
Queue item changes from single Type to Types []QueueItemType slice with HasType(t) and PrimaryType() helper methods; QueueItemType constants reordered so all types are grouped together.
Track metadata configuration schema
src/features/config/config.go, src/features/config/default.go, config.example.yaml
Import config replaces single AllowMissingMetadata bool with AllowMissingMetadata struct containing per-field toggles for Artist, Album, Title, Year, Genre; defaults set in config with Year=true, others false.
Track metadata methods with per-field control
src/music/track.go
EnsureMetadataDefaults and ValidateRequiredMetadata methods now accept per-field boolean parameters to conditionally apply defaults and validation; adds unknownTitle helper for filepath-based title fallback.

Download job, directory import, and import service changes

Layer / File(s) Summary
Download job deferred metadata processing
src/features/downloading/download_job.go
Download jobs no longer default or validate track metadata; missing fields are preserved with defaults applied only during library import.
Directory import refactored for multi-type queue model
src/features/importing/directory_job.go
determineAction rewritten to return multiple queue types and track metadata, apply per-field config defaults, and queue duplicates with optional MissingMetadata type; addTrackToQueue accepts Types slice; import flow integrated with new validation.
Import service action gating and per-field defaults
src/features/importing/service.go
ProcessQueueItem blocks import/replace for MissingMetadata items; ProcessQueueGroup uses HasType for duplicate filtering; replaceTrack/importTrack apply per-field config defaults via EnsureMetadataDefaults.

View models, handlers, and UI template updates

Layer / File(s) Summary
View model and handler conversion to boolean flags
src/features/importing/handlers.go
queueItemView refactored to use status booleans (IsDuplicate, IsMissingMetadata, IsFailedImport, IsManualReview) and UI control flags (ShowReplace/ReplaceEnabled, ShowImport/ImportEnabled, CancelLabel, BlockReason); convertQueueItem derives all flags from item.HasType checks.
Config form for per-field metadata settings
views/config/config_form.html
Import settings UI replaces single "allow missing metadata" checkbox with subsection containing per-field checkboxes for Artist, Album, Title, Year, Genre.
Config handler for per-field metadata submission
src/features/config/handlers.go
UpdateSettings reads per-field form values and constructs AllowMissingMetadata struct for config update.
Queue item card template with boolean flag rendering
views/importing/queue_items.html
Queue item card template updated to render status styling and badges using boolean flags, tooltip from ItemMetadata, and action buttons using flag-based visibility/enablement instead of Type string comparisons.
Grouped queue templates with boolean flag rendering
views/importing/queue_items_grouped_album.html, views/importing/queue_items_grouped_artist.html
Album and artist grouped templates updated to use boolean flags for container styling, badge rendering, audio player selection, and action button visibility/enablement instead of Type string comparisons.

Telegram, lyrics, and tag writing updates

Layer / File(s) Summary
Telegram message and keyboard for multi-type items
src/features/importing/telegram.go
formatQueueItemMessage uses new queueTypeLabel helper to render comma-separated type labels; createInlineKeyboard refactored to use HasType checks with MissingMetadata blocking import/replace actions.
Lyrics service and handlers for new queue model
src/features/lyrics/service.go, src/features/lyrics/handlers.go, src/features/lyrics/lyrics_job.go
Lyrics service populates QueueItem.Types as slice and dispatches on PrimaryType; handlers and job task updated to use PrimaryType instead of Type for classification.
Proactive field clearing in MP3 and FLAC tagging
src/infra/tag/tag_writer.go
Tag writer unconditionally deletes existing metadata frames before conditionally re-adding to prevent stale metadata when fields are cleared.

Feature specification and user documentation

Layer / File(s) Summary
Feature spec and user documentation updates
FEATURE_SPEC_KIT.md, docs/importing.md
Feature spec documents Types slice, HasType and PrimaryType usage, and processing via PrimaryType; user docs describe per-field config, multi-type queue badges, and MissingMetadata blocking import/replace.

🎯 4 (Complex) | ⏱️ ~75 minutes


new feature, refactor

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feat/missing metadata' is vague and generic, using non-descriptive formatting that doesn't clearly convey the scope or primary change of the pull request. Use a more descriptive title that explains the main change, such as 'Support per-field missing metadata configuration' or 'Refactor queue items to support multiple types per item'.
✅ Passed checks (4 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/missing_metadata

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@config.example.yaml`:
- Around line 42-47: The sample config in config.example.yaml has
allow_missing_metadata.year and allow_missing_metadata.genre set to false which
conflicts with the runtime defaults in src/features/config/default.go (where
year and genre are true); update the example file so allow_missing_metadata.year
and allow_missing_metadata.genre are set to true to match the runtime defaults
and ensure consistent import behavior between generated defaults and the example
config.

In `@src/features/config/config.go`:
- Around line 35-47: The config loader fails when users set
import.allow_missing_metadata as a scalar boolean because AllowMissingMetadata
is a struct of per-field booleans; add a compatibility Unmarshal hook by
implementing AllowMissingMetadata.UnmarshalYAML (or UnmarshalJSON) that accepts
either a bool or a mapping and sets all fields to the bool when a scalar is
provided (true → all fields true, false → all fields false), so
Manager.loadConfig's rootNode.Decode(&cfg) succeeds for legacy scalar configs;
update the AllowMissingMetadata type to include this method and ensure it
handles mapping decoding as the existing behavior.

In `@src/features/downloading/download_job.go`:
- Around line 124-125: executeTrackDownload, executeAlbumDownload, and
executeArtistDownload assume track.Artists[0].Artist.Name exists and will panic
when a downloader returns a track with no artist metadata; update those
functions to defensively access artist data by checking len(track.Artists) > 0
and that track.Artists[0] and track.Artists[0].Artist are non-nil before reading
.Name, falling back to an empty string (or a defined helper like
safeArtistName(track)) where name is used (same fix for the occurrences
referenced around lines ~118, ~212-213, ~218, ~327-328, ~332). Ensure any
downstream uses (logging, filenames, tags) handle the empty/fallback value
safely instead of assuming a non-empty artist name.

In `@src/features/importing/directory_job.go`:
- Line 271: The call that queues a FailedImport uses err.Error() even on the
zero-byte-file path where err can be nil; update the logic around
addTrackToQueue (the call using nullTrackForQueue and QueueItemType
FailedImport) to avoid calling err.Error() when err is nil—e.g. compute an error
string only if err != nil (or pass nil/omit the "error" map entry when err is
nil) and pass that safe value into addTrackToQueue so no panic occurs when tag
reading succeeded but file size is zero.

In `@src/features/importing/handlers.go`:
- Around line 365-369: The grouped "Replace Duplicates" flag is being set too
broadly: change the condition that sets hasDuplicates so it only becomes true
when the view both is a duplicate and can actually be replaced; i.e., replace
the check using view.IsDuplicate with a check that the view is duplicate AND
replaceable (e.g., view.IsDuplicate && view.ReplaceEnabled or, if the view
represents a group, check the group's items for any item.ReplaceEnabled == true)
so the bulk replace button only appears when at least one item in the group has
ReplaceEnabled == true while keeping the existing hasImportable logic using
view.ImportEnabled.

In `@src/features/importing/service.go`:
- Around line 334-336: Call EnsureMetadataDefaults(...) on existingTrack using
s.config.Get().Import.AllowMissingMetadata before you compute the
destination/library path and before performing the move/copy so fallback
artist/album/title/year/genre values are applied to path resolution; update both
spots (the current block around EnsureMetadataDefaults at lines ~334-336 and the
other occurrence around ~429-431) to run EnsureMetadataDefaults first, then
build the destination path and perform the file move/copy operations.
- Around line 298-304: The loop that calls ProcessQueueItem can return success
even when nothing was actually replaced because Duplicate+MissingMetadata items
are routed into the "replace" branch but then rejected by ProcessQueueItem; fix
by tracking whether any items were actually processed and surface an error when
none were replaced. In the loop around ProcessQueueItem (the block that checks
action == "replace"/"import"), add a processedCount (and optional failedCount)
increment when ProcessQueueItem returns nil, and after the loop return an error
(or non-2xx result) if processedCount == 0; alternatively pre-filter items by
checking item.HasType(music.MissingMetadata) combined with
item.HasType(music.Duplicate) before calling ProcessQueueItem so you only call
ProcessQueueItem for items it will accept. Ensure references to ProcessQueueItem
and the action selection logic are used to locate and update the code.

In `@src/music/track.go`:
- Line 237: Normalize missing-metadata checks by introducing and using a
whitespace-aware helper (e.g., isBlank(s string) bool { return
strings.TrimSpace(s) == "" }) and an accessor that finds the first non-empty
artist (e.g., firstValidArtist(t *Track) *ArtistEntry) instead of inspecting
only t.Artists[0]; replace all raw == "" checks on fields like Artist.Name,
Album.Name, Track.Title with isBlank(...) and replace index-only checks
(t.Artists[0].Artist == nil / t.Artists[0].Artist.Name) with a loop that returns
the first Artist != nil and !isBlank(Artist.Name) before defaulting/validation
so you don’t overwrite valid later entries and treats whitespace-only values as
missing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 362e6415-279c-45a0-bf97-ed44a165f969

📥 Commits

Reviewing files that changed from the base of the PR and between 1488fe3 and 658efeb.

📒 Files selected for processing (22)
  • FEATURE_SPEC_KIT.md
  • config.example.yaml
  • docs/importing.md
  • src/features/config/config.go
  • src/features/config/default.go
  • src/features/config/handlers.go
  • src/features/downloading/download_job.go
  • src/features/importing/directory_job.go
  • src/features/importing/handlers.go
  • src/features/importing/queue.go
  • src/features/importing/service.go
  • src/features/importing/telegram.go
  • src/features/lyrics/handlers.go
  • src/features/lyrics/lyrics_job.go
  • src/features/lyrics/service.go
  • src/infra/tag/tag_writer.go
  • src/music/queue.go
  • src/music/track.go
  • views/config/config_form.html
  • views/importing/queue_items.html
  • views/importing/queue_items_grouped_album.html
  • views/importing/queue_items_grouped_artist.html

Comment thread config.example.yaml
Comment thread src/features/config/config.go
Comment thread src/features/downloading/download_job.go
Comment thread src/features/importing/directory_job.go
Comment on lines +365 to 369
if view.IsDuplicate {
hasDuplicates = true
} else if view.Type != "failed_import" && view.Type != "missing_metadata" {
}
if view.ImportEnabled {
hasImportable = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

HasDuplicates is too broad for the grouped “Replace Duplicates” action.

These lines mark a group as replaceable whenever any item is duplicate, even if every duplicate has ReplaceEnabled == false because of missing metadata. The grouped templates will still render a bulk replace button for a group that cannot successfully replace anything.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/importing/handlers.go` around lines 365 - 369, The grouped
"Replace Duplicates" flag is being set too broadly: change the condition that
sets hasDuplicates so it only becomes true when the view both is a duplicate and
can actually be replaced; i.e., replace the check using view.IsDuplicate with a
check that the view is duplicate AND replaceable (e.g., view.IsDuplicate &&
view.ReplaceEnabled or, if the view represents a group, check the group's items
for any item.ReplaceEnabled == true) so the bulk replace button only appears
when at least one item in the group has ReplaceEnabled == true while keeping the
existing hasImportable logic using view.ImportEnabled.

Comment thread src/features/importing/service.go
Comment thread src/features/importing/service.go
Comment thread src/music/track.go
@contre95 contre95 merged commit 658efeb into main Jun 12, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant