Normalize server feature set

This commit is contained in:
jeffvli 2024-03-03 22:15:49 -08:00
parent dae2f9bd0a
commit f1f6ccfd02
9 changed files with 63 additions and 30 deletions

View file

@ -0,0 +1,6 @@
export enum ServerFeature {
SMART_PLAYLISTS = 'smartPlaylists',
SONG_LYRICS = 'songLyrics',
}
export type ServerFeatures = Record<Partial<ServerFeature>, boolean>;

View file

@ -59,6 +59,7 @@ import packageJson from '../../../../package.json';
import { z } from 'zod';
import { JFSongListSort, JFSortOrder } from '/@/renderer/api/jellyfin.types';
import isElectron from 'is-electron';
import { ServerFeatures } from '/@/renderer/api/features.types';
const formatCommaDelimitedString = (value: string[]) => {
return value.join(',');
@ -957,7 +958,16 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
throw new Error('Failed to get server info');
}
return { id: apiClientProps.server?.id, version: res.body.Version };
const features: ServerFeatures = {
smartPlaylists: false,
songLyrics: true,
};
return {
features,
id: apiClientProps.server?.id,
version: res.body.Version,
};
};
export const jfController = {

View file

@ -665,6 +665,10 @@ const serverInfo = z.object({
Version: z.string(),
});
export enum JellyfinExtensions {
SONG_LYRICS = 'songLyrics',
}
export const jfType = {
_enum: {
albumArtistList: albumArtistListSort,

View file

@ -49,6 +49,7 @@ import {
ServerInfoArgs,
} from '../types';
import { hasFeature } from '/@/renderer/api/utils';
import { ServerFeature, ServerFeatures } from '/@/renderer/api/features.types';
const authenticate = async (
url: string,
@ -366,7 +367,7 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<PlaylistListResp
if (
customQuery &&
customQuery.smart !== undefined &&
!hasFeature(apiClientProps.server, NavidromeExtensions.SMART_PLAYLISTS)
!hasFeature(apiClientProps.server, ServerFeature.SMART_PLAYLISTS)
) {
customQuery.smart = undefined;
}
@ -481,7 +482,7 @@ const removeFromPlaylist = async (
};
const VERSION_INFO: Array<[string, Record<string, number[]>]> = [
['0.48.0', { [NavidromeExtensions.SMART_PLAYLISTS]: [1] }],
['0.48.0', { [ServerFeature.SMART_PLAYLISTS]: [1] }],
];
const getFeatures = (version: string): Record<string, number[]> => {
@ -518,7 +519,7 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
throw new Error('Failed to ping server');
}
const features: Record<string, number[]> = getFeatures(ping.body.serverVersion!);
const navidromeFeatures: Record<string, number[]> = getFeatures(ping.body.serverVersion!);
if (ping.body.openSubsonic) {
const res = await ssApiClient(apiClientProps).getServerInfo();
@ -528,10 +529,19 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
}
for (const extension of res.body.openSubsonicExtensions) {
features[extension.name] = extension.versions;
navidromeFeatures[extension.name] = extension.versions;
}
}
const features: ServerFeatures = {
smartPlaylists: false,
songLyrics: true,
};
if (navidromeFeatures[NavidromeExtensions.SMART_PLAYLISTS]) {
features[ServerFeature.SMART_PLAYLISTS] = true;
}
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion! };
};

View file

@ -2,7 +2,7 @@ import md5 from 'md5';
import { z } from 'zod';
import { ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
import { ssNormalize } from '/@/renderer/api/subsonic/subsonic-normalize';
import { ssType } from '/@/renderer/api/subsonic/subsonic-types';
import { SubsonicExtensions, ssType } from '/@/renderer/api/subsonic/subsonic-types';
import {
ArtistInfoArgs,
AuthenticationResponse,
@ -27,6 +27,7 @@ import {
StructuredLyric,
} from '/@/renderer/api/types';
import { randomString } from '/@/renderer/utils';
import { ServerFeatures } from '/@/renderer/api/features.types';
const authenticate = async (
url: string,
@ -381,8 +382,13 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
throw new Error('Failed to ping server');
}
const features: ServerFeatures = {
smartPlaylists: false,
songLyrics: false,
};
if (!ping.body.openSubsonic || !ping.body.serverVersion) {
return { version: ping.body.version };
return { features, version: ping.body.version };
}
const res = await ssApiClient(apiClientProps).getServerInfo();
@ -391,9 +397,13 @@ const getServerInfo = async (args: ServerInfoArgs): Promise<ServerInfo> => {
throw new Error('Failed to get server extensions');
}
const features: Record<string, number[]> = {};
const subsonicFeatures: Record<string, number[]> = {};
for (const extension of res.body.openSubsonicExtensions) {
features[extension.name] = extension.versions;
subsonicFeatures[extension.name] = extension.versions;
}
if (subsonicFeatures[SubsonicExtensions.SONG_LYRICS]) {
features.songLyrics = true;
}
return { features, id: apiClientProps.server?.id, version: ping.body.serverVersion };

View file

@ -247,6 +247,12 @@ const structuredLyrics = z.object({
.optional(),
});
export enum SubsonicExtensions {
FORM_POST = 'formPost',
SONG_LYRICS = 'songLyrics',
TRANSCODE_OFFSET = 'transcodeOffset',
}
export const ssType = {
_parameters: {
albumList: albumListParameters,

View file

@ -20,6 +20,7 @@ import {
NDUserListSort,
NDGenreListSort,
} from './navidrome.types';
import { ServerFeatures } from '/@/renderer/api/features.types';
export enum LibraryItem {
ALBUM = 'album',
@ -57,7 +58,7 @@ export type User = {
export type ServerListItem = {
credential: string;
features?: Record<string, number[]>;
features?: ServerFeatures;
id: string;
name: string;
ndCredential?: string;
@ -1144,14 +1145,8 @@ export type FontData = {
export type ServerInfoArgs = BaseEndpointArgs;
export enum SubsonicExtensions {
FORM_POST = 'formPost',
SONG_LYRICS = 'songLyrics',
TRANSCODE_OFFSET = 'transcodeOffset',
}
export type ServerInfo = {
features?: Record<string, number[]>;
features: ServerFeatures;
id?: string;
version: string;
};

View file

@ -3,6 +3,7 @@ import { z } from 'zod';
import { toast } from '/@/renderer/components';
import { useAuthStore } from '/@/renderer/store';
import { ServerListItem } from '/@/renderer/api/types';
import { ServerFeature } from '/@/renderer/api/features.types';
// Since ts-rest client returns a strict response type, we need to add the headers to the body object
export const resultWithHeaders = <ItemType extends z.ZodTypeAny>(itemSchema: ItemType) => {
@ -39,19 +40,10 @@ export const authenticationFailure = (currentServer: ServerListItem | null) => {
}
};
export const hasFeature = (
server: ServerListItem | null,
feature: string,
version = 1,
): boolean => {
export const hasFeature = (server: ServerListItem | null, feature: ServerFeature): boolean => {
if (!server || !server.features) {
return false;
}
const versions = server.features[feature];
if (!versions || versions.length === 0) {
return false;
}
return versions.includes(version);
return server.features[feature];
};

View file

@ -6,7 +6,6 @@ import {
InternetProviderLyricResponse,
FullLyricsMetadata,
LyricGetQuery,
SubsonicExtensions,
StructuredLyric,
ServerType,
} from '/@/renderer/api/types';
@ -16,6 +15,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { api } from '/@/renderer/api';
import isElectron from 'is-electron';
import { hasFeature } from '/@/renderer/api/utils';
import { ServerFeature } from '/@/renderer/api/features.types';
const lyricsIpc = isElectron() ? window.electron.lyrics : null;
@ -113,7 +113,7 @@ export const useSongLyricsBySong = (
source: server?.name ?? 'music server',
};
}
} else if (hasFeature(server, SubsonicExtensions.SONG_LYRICS)) {
} else if (hasFeature(server, ServerFeature.SONG_LYRICS)) {
const subsonicLyrics = await api.controller
.getStructuredLyrics({
apiClientProps: { server, signal },