diff --git a/docker/library-manager/scripts/setup-servers.ts b/docker/library-manager/scripts/setup-servers.ts index cc84e52..1a60fef 100755 --- a/docker/library-manager/scripts/setup-servers.ts +++ b/docker/library-manager/scripts/setup-servers.ts @@ -2,32 +2,66 @@ import { SubsonicClient } from "./util/subsonic.ts"; import { sleep } from "./util/util.ts"; +async function getArtistId( + client: SubsonicClient, + artist: string, +): Promise { + const { xml } = await client.get("getArtists"); + + return xml.querySelector( + `artist[name='${artist.replaceAll("'", "\\'")}']`, + )?.id!; +} + +async function getAlbumId( + client: SubsonicClient, + album: string, +): Promise { + const { xml } = await client.get("getAlbumList2", [ + ["type", "newest"], + ]); + + return xml.querySelector( + `album[name='${album.replaceAll("'", "\\'")}']`, + )?.id!; +} + async function getSongId( client: SubsonicClient, album: string, track: number, ): Promise { - const { xml: albumsXml } = await client.get("getAlbumList2", [ - ["type", "newest"], - ]); - const albumId = albumsXml.querySelector( - `album[name='${album.replaceAll("'", "\\'")}']`, - )?.id; + const albumId = await getAlbumId(client, album); - const { xml: songsXml } = await client.get("getAlbum", [["id", albumId!]]); - return songsXml.querySelector(`song[track='${track}']`)?.id!; + const { xml } = await client.get("getAlbum", [["id", albumId!]]); + return xml.querySelector(`song[track='${track}']`)?.id!; } async function scrobbleTrack( client: SubsonicClient, - songId: string, + album: string, + track: number, ) { + const songId = await getSongId(client, album, track); + await client.get("scrobble", [ ["id", songId!], ["submission", "true"], ]); } +async function starAlbum(client: SubsonicClient, album: string) { + const albumId = await getAlbumId(client, album); + + await client.get("star", [["albumId", albumId]]); +} + +async function starArtist(client: SubsonicClient, artist: string) { + const artistId = await getArtistId(client, artist); + + await client.get("star", [["artistId", artistId]]); +} + async function createPlaylist( client: SubsonicClient, name: string, @@ -44,17 +78,11 @@ async function createPlaylist( } async function setupTestData(client: SubsonicClient) { - await scrobbleTrack( - client, - await getSongId(client, "Retroconnaissance EP", 1), - ); + await scrobbleTrack(client, "Retroconnaissance EP", 1); await sleep(1_000); - await scrobbleTrack( - client, - await getSongId(client, "Retroconnaissance EP", 2), - ); + await scrobbleTrack(client, "Retroconnaissance EP", 2); await sleep(1_000); - await scrobbleTrack(client, await getSongId(client, "Kosmonaut", 1)); + await scrobbleTrack(client, "Kosmonaut", 1); await createPlaylist(client, "Playlist 1", [ { album: "Retroconnaissance EP", track: 2 }, @@ -65,6 +93,9 @@ async function setupTestData(client: SubsonicClient) { { album: "I Don't Know What I'm Doing", track: 10 }, { album: "I Don't Know What I'm Doing", track: 11 }, ]); + + await starAlbum(client, "Kosmonaut"); + await starArtist(client, "Ugress"); } async function setupNavidrome() { diff --git a/lib/app/lists/list_items.dart b/lib/app/lists/list_items.dart index 80339c8..0586562 100644 --- a/lib/app/lists/list_items.dart +++ b/lib/app/lists/list_items.dart @@ -47,9 +47,7 @@ class ArtistListTile extends StatelessWidget { Widget build(BuildContext context) { return ListTile( leading: CircleClip( - child: artist.coverArt != null - ? CoverArtImage(coverArt: artist.coverArt) - : CachedImage(artist.smallImage), + child: CoverArtImage(coverArt: artist.coverArt), ), title: Text(artist.name), subtitle: albumCount != null ? Text('$albumCount albums') : null, diff --git a/lib/app/state/services.dart b/lib/app/state/services.dart index 8009608..b628e2a 100644 --- a/lib/app/state/services.dart +++ b/lib/app/state/services.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import '../../services/sync_services.dart'; +import '../../services/sync_service.dart'; import 'database.dart'; import 'settings.dart'; import 'source.dart'; diff --git a/lib/database/database.dart b/lib/database/database.dart index fbca038..79c714b 100644 --- a/lib/database/database.dart +++ b/lib/database/database.dart @@ -438,8 +438,6 @@ extension ArtistToDb on models.Artist { name: name, starred: Value(starred), coverArt: Value(coverArt), - smallImage: Value(smallImage), - largeImage: Value(largeImage), ); } diff --git a/lib/database/database.g.dart b/lib/database/database.g.dart index 3ff00b8..ab1a11c 100644 --- a/lib/database/database.g.dart +++ b/lib/database/database.g.dart @@ -719,34 +719,8 @@ class Artists extends Table with TableInfo { requiredDuringInsert: false, $customConstraints: '', ); - late final GeneratedColumnWithTypeConverter smallImage = - GeneratedColumn( - 'small_image', - aliasedName, - true, - type: DriftSqlType.string, - requiredDuringInsert: false, - $customConstraints: '', - ).withConverter(Artists.$convertersmallImagen); - late final GeneratedColumnWithTypeConverter largeImage = - GeneratedColumn( - 'large_image', - aliasedName, - true, - type: DriftSqlType.string, - requiredDuringInsert: false, - $customConstraints: '', - ).withConverter(Artists.$converterlargeImagen); @override - List get $columns => [ - sourceId, - id, - name, - starred, - coverArt, - smallImage, - largeImage, - ]; + List get $columns => [sourceId, id, name, starred, coverArt]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -817,18 +791,6 @@ class Artists extends Table with TableInfo { DriftSqlType.string, data['${effectivePrefix}cover_art'], ), - smallImage: Artists.$convertersmallImagen.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}small_image'], - ), - ), - largeImage: Artists.$converterlargeImagen.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, - data['${effectivePrefix}large_image'], - ), - ), ); } @@ -837,12 +799,6 @@ class Artists extends Table with TableInfo { return Artists(attachedDatabase, alias); } - static TypeConverter $convertersmallImage = const UriConverter(); - static TypeConverter $convertersmallImagen = - NullAwareTypeConverter.wrap($convertersmallImage); - static TypeConverter $converterlargeImage = const UriConverter(); - static TypeConverter $converterlargeImagen = - NullAwareTypeConverter.wrap($converterlargeImage); @override List get customConstraints => const [ 'PRIMARY KEY(source_id, id)', @@ -858,8 +814,6 @@ class ArtistsCompanion extends UpdateCompanion { final Value name; final Value starred; final Value coverArt; - final Value smallImage; - final Value largeImage; final Value rowid; const ArtistsCompanion({ this.sourceId = const Value.absent(), @@ -867,8 +821,6 @@ class ArtistsCompanion extends UpdateCompanion { this.name = const Value.absent(), this.starred = const Value.absent(), this.coverArt = const Value.absent(), - this.smallImage = const Value.absent(), - this.largeImage = const Value.absent(), this.rowid = const Value.absent(), }); ArtistsCompanion.insert({ @@ -877,8 +829,6 @@ class ArtistsCompanion extends UpdateCompanion { required String name, this.starred = const Value.absent(), this.coverArt = const Value.absent(), - this.smallImage = const Value.absent(), - this.largeImage = const Value.absent(), this.rowid = const Value.absent(), }) : sourceId = Value(sourceId), id = Value(id), @@ -889,8 +839,6 @@ class ArtistsCompanion extends UpdateCompanion { Expression? name, Expression? starred, Expression? coverArt, - Expression? smallImage, - Expression? largeImage, Expression? rowid, }) { return RawValuesInsertable({ @@ -899,8 +847,6 @@ class ArtistsCompanion extends UpdateCompanion { if (name != null) 'name': name, if (starred != null) 'starred': starred, if (coverArt != null) 'cover_art': coverArt, - if (smallImage != null) 'small_image': smallImage, - if (largeImage != null) 'large_image': largeImage, if (rowid != null) 'rowid': rowid, }); } @@ -911,8 +857,6 @@ class ArtistsCompanion extends UpdateCompanion { Value? name, Value? starred, Value? coverArt, - Value? smallImage, - Value? largeImage, Value? rowid, }) { return ArtistsCompanion( @@ -921,8 +865,6 @@ class ArtistsCompanion extends UpdateCompanion { name: name ?? this.name, starred: starred ?? this.starred, coverArt: coverArt ?? this.coverArt, - smallImage: smallImage ?? this.smallImage, - largeImage: largeImage ?? this.largeImage, rowid: rowid ?? this.rowid, ); } @@ -945,16 +887,6 @@ class ArtistsCompanion extends UpdateCompanion { if (coverArt.present) { map['cover_art'] = Variable(coverArt.value); } - if (smallImage.present) { - map['small_image'] = Variable( - Artists.$convertersmallImagen.toSql(smallImage.value), - ); - } - if (largeImage.present) { - map['large_image'] = Variable( - Artists.$converterlargeImagen.toSql(largeImage.value), - ); - } if (rowid.present) { map['rowid'] = Variable(rowid.value); } @@ -969,8 +901,6 @@ class ArtistsCompanion extends UpdateCompanion { ..write('name: $name, ') ..write('starred: $starred, ') ..write('coverArt: $coverArt, ') - ..write('smallImage: $smallImage, ') - ..write('largeImage: $largeImage, ') ..write('rowid: $rowid') ..write(')')) .toString(); @@ -2967,8 +2897,6 @@ typedef $ArtistsCreateCompanionBuilder = required String name, Value starred, Value coverArt, - Value smallImage, - Value largeImage, Value rowid, }); typedef $ArtistsUpdateCompanionBuilder = @@ -2978,8 +2906,6 @@ typedef $ArtistsUpdateCompanionBuilder = Value name, Value starred, Value coverArt, - Value smallImage, - Value largeImage, Value rowid, }); @@ -3015,18 +2941,6 @@ class $ArtistsFilterComposer extends Composer<_$SubtracksDatabase, Artists> { column: $table.coverArt, builder: (column) => ColumnFilters(column), ); - - ColumnWithTypeConverterFilters get smallImage => - $composableBuilder( - column: $table.smallImage, - builder: (column) => ColumnWithTypeConverterFilters(column), - ); - - ColumnWithTypeConverterFilters get largeImage => - $composableBuilder( - column: $table.largeImage, - builder: (column) => ColumnWithTypeConverterFilters(column), - ); } class $ArtistsOrderingComposer extends Composer<_$SubtracksDatabase, Artists> { @@ -3061,16 +2975,6 @@ class $ArtistsOrderingComposer extends Composer<_$SubtracksDatabase, Artists> { column: $table.coverArt, builder: (column) => ColumnOrderings(column), ); - - ColumnOrderings get smallImage => $composableBuilder( - column: $table.smallImage, - builder: (column) => ColumnOrderings(column), - ); - - ColumnOrderings get largeImage => $composableBuilder( - column: $table.largeImage, - builder: (column) => ColumnOrderings(column), - ); } class $ArtistsAnnotationComposer @@ -3096,18 +3000,6 @@ class $ArtistsAnnotationComposer GeneratedColumn get coverArt => $composableBuilder(column: $table.coverArt, builder: (column) => column); - - GeneratedColumnWithTypeConverter get smallImage => - $composableBuilder( - column: $table.smallImage, - builder: (column) => column, - ); - - GeneratedColumnWithTypeConverter get largeImage => - $composableBuilder( - column: $table.largeImage, - builder: (column) => column, - ); } class $ArtistsTableManager @@ -3146,8 +3038,6 @@ class $ArtistsTableManager Value name = const Value.absent(), Value starred = const Value.absent(), Value coverArt = const Value.absent(), - Value smallImage = const Value.absent(), - Value largeImage = const Value.absent(), Value rowid = const Value.absent(), }) => ArtistsCompanion( sourceId: sourceId, @@ -3155,8 +3045,6 @@ class $ArtistsTableManager name: name, starred: starred, coverArt: coverArt, - smallImage: smallImage, - largeImage: largeImage, rowid: rowid, ), createCompanionCallback: @@ -3166,8 +3054,6 @@ class $ArtistsTableManager required String name, Value starred = const Value.absent(), Value coverArt = const Value.absent(), - Value smallImage = const Value.absent(), - Value largeImage = const Value.absent(), Value rowid = const Value.absent(), }) => ArtistsCompanion.insert( sourceId: sourceId, @@ -3175,8 +3061,6 @@ class $ArtistsTableManager name: name, starred: starred, coverArt: coverArt, - smallImage: smallImage, - largeImage: largeImage, rowid: rowid, ), withReferenceMapper: (p0) => p0 diff --git a/lib/database/tables.drift b/lib/database/tables.drift index a661d71..8fbd828 100644 --- a/lib/database/tables.drift +++ b/lib/database/tables.drift @@ -66,8 +66,6 @@ CREATE TABLE artists( name TEXT NOT NULL COLLATE NOCASE, starred DATETIME, cover_art TEXT, - small_image TEXT MAPPED BY `const UriConverter()`, - large_image TEXT MAPPED BY `const UriConverter()`, PRIMARY KEY (source_id, id), FOREIGN KEY (source_id) REFERENCES sources (id) ON DELETE CASCADE ) WITH Artist; diff --git a/lib/images/images.dart b/lib/images/images.dart index 51f7040..d050d11 100644 --- a/lib/images/images.dart +++ b/lib/images/images.dart @@ -1,9 +1,7 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:material_symbols_icons/symbols.dart'; -import 'package:octo_image/octo_image.dart'; import '../app/state/settings.dart'; import '../app/state/source.dart'; @@ -23,62 +21,14 @@ class CoverArtImage extends HookConsumerWidget { final source = ref.watch(sourceProvider); final sourceId = ref.watch(sourceIdProvider); - final imageProviderKeys = [source, sourceId, coverArt, thumbnail]; - final buildImageProvider = useCallback( - () => CachedNetworkImageProvider( - coverArt != null - ? source.coverArtUri(coverArt!, thumbnail: thumbnail).toString() - : 'https://placehold.net/400x400.png', - cacheKey: '$sourceId$coverArt$thumbnail', - ), - imageProviderKeys, - ); - - final imageProvider = useState(buildImageProvider()); - useEffect( - () { - imageProvider.value = buildImageProvider(); - return; - }, - imageProviderKeys, - ); + final imageUrl = coverArt != null + ? source.coverArtUri(coverArt!, thumbnail: thumbnail).toString() + : 'https://placehold.net/400x400.png'; return BaseImage( - image: imageProvider.value, - ); - } -} - -class CachedImage extends HookConsumerWidget { - const CachedImage( - this.uri, { - super.key, - }); - - final Uri? uri; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final imageProviderKeys = [uri]; - final buildImageProvider = useCallback( - () { - final imageUrl = uri?.toString() ?? 'https://placehold.net/400x400.png'; - return CachedNetworkImageProvider(imageUrl, cacheKey: imageUrl); - }, - imageProviderKeys, - ); - - final imageProvider = useState(buildImageProvider()); - useEffect( - () { - imageProvider.value = buildImageProvider(); - return; - }, - imageProviderKeys, - ); - - return BaseImage( - image: imageProvider.value, + imageUrl: imageUrl, + // can't use the URL because of token auth, which is a cache-buster + cacheKey: '$sourceId$coverArt$thumbnail', ); } } @@ -86,20 +36,23 @@ class CachedImage extends HookConsumerWidget { class BaseImage extends HookConsumerWidget { const BaseImage({ super.key, - required this.image, + required this.imageUrl, + this.cacheKey, this.fit = BoxFit.cover, }); - final ImageProvider image; + final String imageUrl; + final String? cacheKey; final BoxFit fit; @override Widget build(BuildContext context, WidgetRef ref) { - return OctoImage( - image: image, - placeholderBuilder: (context) => Icon(Symbols.cached_rounded), - errorBuilder: (context, error, trace) => Icon(Icons.error), - fit: fit, + return CachedNetworkImage( + imageUrl: imageUrl, + cacheKey: cacheKey, + placeholder: (context, url) => Icon(Symbols.cached_rounded), + errorWidget: (context, url, error) => Icon(Icons.error), + fit: BoxFit.cover, fadeOutDuration: Duration(milliseconds: 100), fadeInDuration: Duration(milliseconds: 200), ); diff --git a/lib/services/sync_services.dart b/lib/services/sync_service.dart similarity index 100% rename from lib/services/sync_services.dart rename to lib/services/sync_service.dart diff --git a/lib/sources/models.dart b/lib/sources/models.dart index 45bd88f..e2930f0 100644 --- a/lib/sources/models.dart +++ b/lib/sources/models.dart @@ -9,8 +9,6 @@ abstract class Artist with _$Artist { required String name, DateTime? starred, String? coverArt, - Uri? smallImage, - Uri? largeImage, }) = _Artist; } diff --git a/lib/sources/models.freezed.dart b/lib/sources/models.freezed.dart index 2eb6c41..c1d3f71 100644 --- a/lib/sources/models.freezed.dart +++ b/lib/sources/models.freezed.dart @@ -14,7 +14,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$Artist { - String get id; String get name; DateTime? get starred; String? get coverArt; Uri? get smallImage; Uri? get largeImage; + String get id; String get name; DateTime? get starred; String? get coverArt; /// Create a copy of Artist /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -25,16 +25,16 @@ $ArtistCopyWith get copyWith => _$ArtistCopyWithImpl(this as Art @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is Artist&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.starred, starred) || other.starred == starred)&&(identical(other.coverArt, coverArt) || other.coverArt == coverArt)&&(identical(other.smallImage, smallImage) || other.smallImage == smallImage)&&(identical(other.largeImage, largeImage) || other.largeImage == largeImage)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is Artist&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.starred, starred) || other.starred == starred)&&(identical(other.coverArt, coverArt) || other.coverArt == coverArt)); } @override -int get hashCode => Object.hash(runtimeType,id,name,starred,coverArt,smallImage,largeImage); +int get hashCode => Object.hash(runtimeType,id,name,starred,coverArt); @override String toString() { - return 'Artist(id: $id, name: $name, starred: $starred, coverArt: $coverArt, smallImage: $smallImage, largeImage: $largeImage)'; + return 'Artist(id: $id, name: $name, starred: $starred, coverArt: $coverArt)'; } @@ -45,7 +45,7 @@ abstract mixin class $ArtistCopyWith<$Res> { factory $ArtistCopyWith(Artist value, $Res Function(Artist) _then) = _$ArtistCopyWithImpl; @useResult $Res call({ - String id, String name, DateTime? starred, String? coverArt, Uri? smallImage, Uri? largeImage + String id, String name, DateTime? starred, String? coverArt }); @@ -62,15 +62,13 @@ class _$ArtistCopyWithImpl<$Res> /// Create a copy of Artist /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? starred = freezed,Object? coverArt = freezed,Object? smallImage = freezed,Object? largeImage = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? starred = freezed,Object? coverArt = freezed,}) { return _then(_self.copyWith( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,starred: freezed == starred ? _self.starred : starred // ignore: cast_nullable_to_non_nullable as DateTime?,coverArt: freezed == coverArt ? _self.coverArt : coverArt // ignore: cast_nullable_to_non_nullable -as String?,smallImage: freezed == smallImage ? _self.smallImage : smallImage // ignore: cast_nullable_to_non_nullable -as Uri?,largeImage: freezed == largeImage ? _self.largeImage : largeImage // ignore: cast_nullable_to_non_nullable -as Uri?, +as String?, )); } @@ -155,10 +153,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, DateTime? starred, String? coverArt, Uri? smallImage, Uri? largeImage)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String id, String name, DateTime? starred, String? coverArt)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _Artist() when $default != null: -return $default(_that.id,_that.name,_that.starred,_that.coverArt,_that.smallImage,_that.largeImage);case _: +return $default(_that.id,_that.name,_that.starred,_that.coverArt);case _: return orElse(); } @@ -176,10 +174,10 @@ return $default(_that.id,_that.name,_that.starred,_that.coverArt,_that.smallImag /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String id, String name, DateTime? starred, String? coverArt, Uri? smallImage, Uri? largeImage) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String id, String name, DateTime? starred, String? coverArt) $default,) {final _that = this; switch (_that) { case _Artist(): -return $default(_that.id,_that.name,_that.starred,_that.coverArt,_that.smallImage,_that.largeImage);case _: +return $default(_that.id,_that.name,_that.starred,_that.coverArt);case _: throw StateError('Unexpected subclass'); } @@ -196,10 +194,10 @@ return $default(_that.id,_that.name,_that.starred,_that.coverArt,_that.smallImag /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, DateTime? starred, String? coverArt, Uri? smallImage, Uri? largeImage)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String id, String name, DateTime? starred, String? coverArt)? $default,) {final _that = this; switch (_that) { case _Artist() when $default != null: -return $default(_that.id,_that.name,_that.starred,_that.coverArt,_that.smallImage,_that.largeImage);case _: +return $default(_that.id,_that.name,_that.starred,_that.coverArt);case _: return null; } @@ -211,15 +209,13 @@ return $default(_that.id,_that.name,_that.starred,_that.coverArt,_that.smallImag class _Artist implements Artist { - const _Artist({required this.id, required this.name, this.starred, this.coverArt, this.smallImage, this.largeImage}); + const _Artist({required this.id, required this.name, this.starred, this.coverArt}); @override final String id; @override final String name; @override final DateTime? starred; @override final String? coverArt; -@override final Uri? smallImage; -@override final Uri? largeImage; /// Create a copy of Artist /// with the given fields replaced by the non-null parameter values. @@ -231,16 +227,16 @@ _$ArtistCopyWith<_Artist> get copyWith => __$ArtistCopyWithImpl<_Artist>(this, _ @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _Artist&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.starred, starred) || other.starred == starred)&&(identical(other.coverArt, coverArt) || other.coverArt == coverArt)&&(identical(other.smallImage, smallImage) || other.smallImage == smallImage)&&(identical(other.largeImage, largeImage) || other.largeImage == largeImage)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _Artist&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.starred, starred) || other.starred == starred)&&(identical(other.coverArt, coverArt) || other.coverArt == coverArt)); } @override -int get hashCode => Object.hash(runtimeType,id,name,starred,coverArt,smallImage,largeImage); +int get hashCode => Object.hash(runtimeType,id,name,starred,coverArt); @override String toString() { - return 'Artist(id: $id, name: $name, starred: $starred, coverArt: $coverArt, smallImage: $smallImage, largeImage: $largeImage)'; + return 'Artist(id: $id, name: $name, starred: $starred, coverArt: $coverArt)'; } @@ -251,7 +247,7 @@ abstract mixin class _$ArtistCopyWith<$Res> implements $ArtistCopyWith<$Res> { factory _$ArtistCopyWith(_Artist value, $Res Function(_Artist) _then) = __$ArtistCopyWithImpl; @override @useResult $Res call({ - String id, String name, DateTime? starred, String? coverArt, Uri? smallImage, Uri? largeImage + String id, String name, DateTime? starred, String? coverArt }); @@ -268,15 +264,13 @@ class __$ArtistCopyWithImpl<$Res> /// Create a copy of Artist /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? starred = freezed,Object? coverArt = freezed,Object? smallImage = freezed,Object? largeImage = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? starred = freezed,Object? coverArt = freezed,}) { return _then(_Artist( id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,starred: freezed == starred ? _self.starred : starred // ignore: cast_nullable_to_non_nullable as DateTime?,coverArt: freezed == coverArt ? _self.coverArt : coverArt // ignore: cast_nullable_to_non_nullable -as String?,smallImage: freezed == smallImage ? _self.smallImage : smallImage // ignore: cast_nullable_to_non_nullable -as Uri?,largeImage: freezed == largeImage ? _self.largeImage : largeImage // ignore: cast_nullable_to_non_nullable -as Uri?, +as String?, )); } diff --git a/lib/sources/subsonic/mapping.dart b/lib/sources/subsonic/mapping.dart index 1ec5de4..7ba00ed 100644 --- a/lib/sources/subsonic/mapping.dart +++ b/lib/sources/subsonic/mapping.dart @@ -2,21 +2,11 @@ import 'package:xml/xml.dart'; import '../models.dart'; -Uri? uriOrNullParse(String? value) { - if (value == null || value.trim().isEmpty) { - return null; - } - - return Uri.tryParse(value); -} - -Artist mapArtist(XmlElement e, XmlElement? info) => Artist( +Artist mapArtist(XmlElement e) => Artist( id: e.getAttribute('id')!, name: e.getAttribute('name')!, starred: DateTime.tryParse(e.getAttribute('starred').toString()), coverArt: e.getAttribute('coverArt'), - smallImage: uriOrNullParse(info?.getElement('smallImageUrl')?.innerText), - largeImage: uriOrNullParse(info?.getElement('largeImageUrl')?.innerText), ); Album mapAlbum( diff --git a/lib/sources/subsonic/source.dart b/lib/sources/subsonic/source.dart index 8a8ef50..1a674d8 100644 --- a/lib/sources/subsonic/source.dart +++ b/lib/sources/subsonic/source.dart @@ -42,19 +42,13 @@ class SubsonicSource implements MusicSource { @override Stream allArtists() async* { - final getArtistsRes = await _pool.withResource( + final res = await _pool.withResource( () => client.get('getArtists'), ); - yield* _pool.forEach(getArtistsRes.xml.findAllElements('artist'), ( - artist, - ) async { - final res = await client.get('getArtistInfo2', { - 'id': artist.getAttribute('id')!, - }); - - return mapArtist(artist, res.xml.getElement('artistInfo2')); - }); + yield* Stream.fromIterable( + res.xml.findAllElements('artist').map(mapArtist), + ); } @override diff --git a/test/services/sync_service_test.dart b/test/services/sync_service_test.dart index 5b2b21c..7307932 100644 --- a/test/services/sync_service_test.dart +++ b/test/services/sync_service_test.dart @@ -1,5 +1,5 @@ import 'package:subtracks/database/database.dart'; -import 'package:subtracks/services/sync_services.dart'; +import 'package:subtracks/services/sync_service.dart'; import 'package:subtracks/sources/subsonic/source.dart'; import 'package:test/test.dart'; diff --git a/test/sources/subsonic_test.dart b/test/sources/subsonic_test.dart index 4220cdd..ae7c82f 100644 --- a/test/sources/subsonic_test.dart +++ b/test/sources/subsonic_test.dart @@ -5,7 +5,7 @@ import 'package:test/test.dart'; import '../util/subsonic.dart'; void main() { - groupByTestServer((client) { + groupByTestServer((server, client) { late SubsonicSource source; setUp(() async { @@ -29,7 +29,7 @@ void main() { expect(kosmo.created.compareTo(DateTime.now()), lessThan(0)); expect(kosmo.coverArt?.length, greaterThan(0)); expect(kosmo.year, equals(2006)); - expect(kosmo.starred, isNull); + expect(kosmo.starred?.compareTo(DateTime.now()), lessThan(0)); expect(kosmo.genre, equals('Electronic')); final retro = items.firstWhere( @@ -39,6 +39,9 @@ void main() { (a) => a.name == "I Don't Know What I'm Doing", ); + expect(retro.starred, isNull); + expect(dunno.starred, isNull); + expect(kosmo.recentRank, equals(0)); expect(kosmo.frequentRank, equals(1)); @@ -53,6 +56,19 @@ void main() { final items = await source.allArtists().toList(); expect(items.length, equals(2)); + + final brad = items.firstWhere((a) => a.name == 'Brad Sucks'); + + expect(brad.id.length, greaterThan(0)); + expect(brad.starred, isNull); + + if (![Servers.gonic].contains(server)) { + expect(brad.coverArt?.length, greaterThan(0)); + } + + final ugress = items.firstWhere((a) => a.name == 'Ugress'); + + expect(ugress.starred?.compareTo(DateTime.now()), lessThan(0)); }); test('allSongs', () async { diff --git a/test/util/subsonic.dart b/test/util/subsonic.dart index e6ee8cb..220594d 100644 --- a/test/util/subsonic.dart +++ b/test/util/subsonic.dart @@ -23,12 +23,14 @@ Map testServerClients() => { ), }; -void groupByTestServer(void Function(SubsonicClient client) callback) { +void groupByTestServer( + void Function(Servers server, SubsonicClient client) callback, +) { final clients = testServerClients(); for (final MapEntry(key: server, value: client) in clients.entries) { group(server.name, () { - callback(client); + callback(server, client); }); } }