+ return \%matches;
+}
+
+# Given a track MBID, return a hash reference containing all "related"
+# tracks in the MPD database. The hash keys are filenames.
+#
+# Currently tracks are considered "related" if their associated recordings
+# have at least one work in common.
+sub get_tracks_by_track_mbid {
+ my ($mbid, $tagname) = (@_, "MUSICBRAINZ_RELEASETRACKID");
+ my %source;
+ my %matches;
+ my $entry;
+
+ return \%matches unless ($mbid);
+ MPD::exec("search", "($tagname == \"$mbid\")");
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ add_track_metadata(\%source, $1, $2);
+ }
+ }
+
+ # Always include the current track
+ $matches{$source{file}} = \%source;
+
+ # Find all tracks related by work
+ foreach my $mbid (get_track_metadata(\%source, "MUSICBRAINZ_WORKID")) {
+ my $related = get_tracks_by_work_mbid($mbid);
+ foreach (keys %$related) {
+ $matches{$_} //= $related->{$_};
+ }
+ }
+
+ return \%matches;
+}
+
+sub get_tracks_by_recording_mbid {
+ return get_tracks_by_track_mbid($_[0], "MUSICBRAINZ_TRACKID");
+}
+
+# Given a release MBID, return a hash reference containing all its
+# associated tracks in the MPD database. The hash keys are filenames.
+sub get_tracks_by_release_mbid {
+ my ($mbid) = @_;
+ my %matches;
+ my $entry;
+
+ return \%matches unless ($mbid);
+ MPD::exec("search", "(MUSICBRAINZ_ALBUMID == \"$mbid\")");
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ if ($1 eq "file") {
+ if (exists($matches{$2})) {
+ $entry = $matches{$2};
+ } else {
+ $entry = {};
+ $matches{$2} = $entry;
+ }
+ }
+
+ add_track_metadata($entry, $1, $2);
+ }
+ }
+
+ return \%matches;
+}
+
+# Insert the given entry into the referenced hash if it represents a
+# standalone recording (not associated with a release). The recording
+# MBID is used as the hash key.
+sub check_standalone {
+ my ($outhash, $entry) = @_;
+ my ($mbid) = get_track_metadata($entry, "MUSICBRAINZ_TRACKID");
+
+ return if exists $entry->{MUSICBRAINZ_ALBUMID};
+ $outhash->{$mbid} = $entry if ($mbid);
+}
+
+# Given an artist MBID, return a list of two hash refererences. The
+# first contains the associated releases in the MPD database and the
+# hash keys are release MBIDs. The second contains the artist's
+# standalone recordings and the hash keys are recording MBIDs.
+#
+# In scalar context only the release hash is returned.
+#
+# Since MPD returns results on a per-track basis, each entry in the
+# hash has the metadata for one unspecified track from that release.
+sub get_releases_by_artist_mbid {
+ my (%releases, %standalones);
+ my $entry;
+
+ foreach my $mbid (@_) {
+ MPD::exec("search", "(MUSICBRAINZ_ARTISTID == \"$mbid\")");
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ if ($1 eq "file") {
+ check_standalone(\%standalones, $entry);
+ $entry = {};
+ } elsif ($1 eq "MUSICBRAINZ_ALBUMID") {
+ $releases{$2} //= $entry;
+ }
+
+ add_track_metadata($entry, $1, $2);
+ }
+ }
+ check_standalone(\%standalones, $entry);
+ }
+
+ return wantarray ? (\%releases, values %standalones) : \%releases;
+}
+
+# Given a filename, return the IDs (if any) for that file in the
+# current MPD play queue.
+sub get_ids_by_filename {
+ my ($file) = @_;
+ my @results = ();
+
+ MPD::exec("playlistfind", "file", $file);
+ while (<$sock>) {
+ last if (/^OK/);
+ die($_) if (/^ACK/);
+
+ if (/^(\w+): (.*)$/) {
+ push @results, $2 if ($1 eq "Id");
+ }
+ }
+
+ return @results;
+}
+
+sub update_entry_ids {
+ my @notqueued = ();
+
+ foreach my $entry (@_) {
+ unless (exists $entry->{Id}) {
+ my ($id) = get_ids_by_filename($entry->{file});
+ if (defined $id) {
+ $entry->{Id} = $id;
+ } else {
+ push @notqueued, $entry;
+ next;
+ }
+ }
+ }
+
+ return @notqueued;
+}
+
+# albumsort(matches, a, b)
+#
+# Sort hash keys (a, b) by disc/track number for album menus.
+sub albumsort {
+ my ($matches, $a, $b) = @_;
+
+ return $matches->{$a}->{Disc} <=> $matches->{$b}->{Disc}
+ || $matches->{$a}->{Track} <=> $matches->{$b}->{Track}
+ || $a cmp $b;
+}
+
+# datesort(matches, a, b)
+#
+# Sort hash keys (a, b) by release date
+sub datesort {
+ my ($matches, $a, $b) = @_;
+
+ return $matches->{$a}->{Date} cmp $matches->{$b}->{Date}
+ || $a cmp $b;
+}
+
+# menu_trackname(entry)
+#
+# Format the track name for display in an FVWM menu, where entry
+# is a hash reference containing the track metadata.
+sub menu_trackname {
+ my ($entry) = @_;
+ my $fmt = "$entry->{trackfmt}$entry->{Artist} - $entry->{Title}";
+ return "$entry->{thumb}" . fvwm_label_escape($fmt);
+}