Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace Track.artist and Track.extraArtists with Track.artists #1681

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

Lucki
Copy link
Contributor

@Lucki Lucki commented Aug 21, 2024

@nukeop
Copy link
Owner

nukeop commented Aug 22, 2024

Thanks for contributing this. Is it ready for review?

@Lucki
Copy link
Contributor Author

Lucki commented Aug 22, 2024

Yes. Please let me know when you find something non-functional.

Edit: Found one - the MPRIS thing is showing unknown artist
Edit2: Another one - A discogs track without an artist but with extraartists, wanna use the album artist when no specific track artist is available.
Edit3: LastFM track search needs adjustments converting to internal

Comment on lines +79 to +80
// @ts-expect-error NuclearMeta is not a descendant of Track, but Track.artists should be available here anyway
'xesam:artist': track.artists ?? [track.artist]
Copy link
Owner

Choose a reason for hiding this comment

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

NuclearMeta should be updated the same way. I'm afraid this is also going to need that:

  • Scanned local library tracks will need to be migrated on first run
  • Scanner will need to return data in this new format

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I adjusted the obvious bits, although blind (no manual testing).

As written in the Discord thread and as I have no usage experience with that part of the program, I think it's better you take this part.

Comment on lines 179 to 186
function getDownloadsBackwardsCompatible(): Download[] {
const downloads: Download[] = store.get('downloads');

downloads.forEach(download => {
// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
if (download.track.artists || !download.track.artist) {
return;
}

// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
if (download.track.artist) {
// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
download.track.artists = _.isString(download.track.artist) ? [download.track.artist] : [download.track.artist.name];
}

// Assuming we have `extraArtists` on a track, we must had an `artist` which
// was already saved into `artists`, so this `track.artists` shouldn't be undefined
// @ts-expect-error For backwards compatibility we're trying to parse an invalid field
download.track.extraArtists?.forEach(artist => {
download.track.artists.push(artist);
});
});

return downloads;
}
Copy link
Owner

Choose a reason for hiding this comment

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

This function does the exact same thing as the one in playlists.ts and almost the same as the one in favorites.ts. The common parts of this logic should be extracted and can live in e.g. packages/app/app/actions/helpers.ts.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not quite. All three are slightly different:

Download[]: { track: Track }[]
Playlist[]: { tracks: Track[] }[]
Favorites: { tracks: Track[] }

I tried combining all into a generic function but wasn't successful with it.

Just for laughs
export interface TrackMember {
  track: Track;
}

export interface TracksMember {
  tracks: Track[];
}

export interface Favorites {
  albums: Album[];
  artists: Artist[];
  tracks: Track[];
}

function isTracksMember(config: TrackMember | TracksMember): config is TracksMember {
  return (config as TracksMember).tracks !== undefined;
}

function isTrackMember(config: TrackMember | TracksMember): config is TrackMember {
  return (config as TrackMember).track !== undefined;
}

function isTracksArrayMember(config: TrackMember | TracksMember | Favorites): config is Favorites {
  return (config as Favorites).tracks !== undefined && (config as Favorites).tracks instanceof Array;
}

function rewriteTrackArtists(track: Track) {
  // @ts-expect-error For backwards compatibility we're trying to parse an invalid field
  if (track.artists || !track.artist) {
    // New format already present, do nothing
    return;
  }

  // @ts-expect-error For backwards compatibility we're trying to parse an invalid field
  track.artists = _.isString(track.artist) ? [track.artist] : [track.artist.name];

  // @ts-expect-error For backwards compatibility we're trying to parse an invalid field
  track.artists = track.artists.concat(track.extraArtists?.map(artist));
}

/**
+* Helper function to read the old track format into the new format.
+*
+* `Track.artist` and `Track.extraArtists` are written into {@link Track.artists}
+*/
export function getFromStoreBackwardsCompatible<T extends Favorites | TrackMember[] | TracksMember[]>(configName: 'downloads' | 'favorites' | 'playlists'): T {
  let config: T = store.get(configName);

  if (config instanceof Array) {
    // Playlist[] || Download[]
    config = config.map((member: TrackMember | TracksMember) => {
      if (isTrackMember(member)) {
        // Download[]
        rewriteTrackArtists(member.track);
      } else if (isTracksMember(member)) {
        // Playlist[]
        member.tracks.forEach(track => {
          rewriteTrackArtists(track);
        });
      }
      return member;
    });
  } else if (isTracksArrayMember(config)) {
    // Favorites: { tracks: Track[] }
    config.tracks = config.tracks.map(track => {
      rewriteTrackArtists(track);
      return track;
    });
    config.albums = config.albums.map(album => {
      album.tracklist = album.tracklist?.map(track => {
        rewriteTrackArtists(track);
        return track;
      });
      return album;
    });
  }

  return config;
}

Comment on lines 148 to 137
album.tracklist?.forEach(track => {
if (track.artists || !track.artist) {
return;
}

if (track.artist) {
track.artists = _.isString(track.artist) ? [track.artist] : [track.artist.name];
}

// Assuming we have `extraArtists` on a track, we must had an `artist` which
// was already saved into `artists`, so this `track.artists` shouldn't be undefined
track.extraArtists?.forEach(artist => {
track.artists.push(artist);
});
});
});

Copy link
Owner

Choose a reason for hiding this comment

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

This part can use the function that can be extracted from all getXBackwardsCompatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in the Discord thread, this is very strange.
I moved it in a separate helper function and then the tests start failing because the track is suddenly undefined.

Comment on lines 197 to 199
download.track.extraArtists?.forEach(artist => {
download.track.artists.push(artist);
});
Copy link
Owner

Choose a reason for hiding this comment

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

Instead of mutating by using forEach/push, you can use map and assign what map returns.

Copy link
Owner

Choose a reason for hiding this comment

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

This also applies to other places where forEach is used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed them all to use map now.
Aside from inside the helper functions there was only one single occurrence when reading from the DB.

@nukeop nukeop added the needs changes The author needs to make changes to this pull request. label Aug 25, 2024
@nukeop
Copy link
Owner

nukeop commented Aug 28, 2024

I'm still working on the track table refactor, after that I'll get back to this.

@nukeop nukeop added under review This pull request is being reviewed by maintainers. and removed needs changes The author needs to make changes to this pull request. labels Aug 28, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
under review This pull request is being reviewed by maintainers.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants