bring in database

switch to just using source models (no extra db fields)
start re-implementing sync service
This commit is contained in:
austinried
2025-11-07 11:45:13 +09:00
parent f1c734d432
commit 0e6acbed0f
18 changed files with 6747 additions and 625 deletions

View File

@@ -1,32 +1,21 @@
// ignore_for_file: annotate_overrides
import 'package:freezed_annotation/freezed_annotation.dart';
part 'models.freezed.dart';
part 'models.g.dart';
mixin Starred {
DateTime? get starred;
}
mixin CoverArt {
String? get coverArt;
}
@freezed
abstract class SourceItem with _$SourceItem {
@With<Starred>()
const factory SourceItem.artist({
abstract class Artist with _$Artist {
const factory Artist({
required String id,
required String name,
DateTime? starred,
Uri? smallImage,
Uri? largeImage,
}) = SourceArtist;
}) = _Artist;
}
@With<Starred>()
@With<CoverArt>()
const factory SourceItem.album({
@freezed
abstract class Album with _$Album {
const factory Album({
required String id,
String? artistId,
required String name,
@@ -38,10 +27,12 @@ abstract class SourceItem with _$SourceItem {
String? genre,
int? frequentRank,
int? recentRank,
}) = SourceAlbum;
}) = _Album;
}
@With<CoverArt>()
const factory SourceItem.playlist({
@freezed
abstract class Playlist with _$Playlist {
const factory Playlist({
required String id,
required String name,
String? comment,
@@ -50,11 +41,12 @@ abstract class SourceItem with _$SourceItem {
String? coverArt,
String? owner,
bool? public,
}) = SourcePlaylist;
}) = _Playlist;
}
@With<Starred>()
@With<CoverArt>()
const factory SourceItem.song({
@freezed
abstract class Song with _$Song {
const factory Song({
required String id,
String? albumId,
String? artistId,
@@ -67,20 +59,14 @@ abstract class SourceItem with _$SourceItem {
DateTime? starred,
String? genre,
String? coverArt,
}) = SourceSong;
factory SourceItem.fromJson(Map<String, dynamic> json) =>
_$SourceItemFromJson(json);
}) = _Song;
}
@freezed
abstract class SourcePlaylistSong with _$SourcePlaylistSong {
const factory SourcePlaylistSong({
abstract class PlaylistSong with _$PlaylistSong {
const factory PlaylistSong({
required String playlistId,
required String songId,
required int position,
}) = _SourcePlaylistSong;
factory SourcePlaylistSong.fromJson(Map<String, dynamic> json) =>
_$SourcePlaylistSongFromJson(json);
}) = _PlaylistSong;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,142 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'models.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
SourceArtist _$SourceArtistFromJson(Map<String, dynamic> json) => SourceArtist(
id: json['id'] as String,
name: json['name'] as String,
starred: json['starred'] == null
? null
: DateTime.parse(json['starred'] as String),
smallImage: json['smallImage'] == null
? null
: Uri.parse(json['smallImage'] as String),
largeImage: json['largeImage'] == null
? null
: Uri.parse(json['largeImage'] as String),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$SourceArtistToJson(SourceArtist instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'starred': instance.starred?.toIso8601String(),
'smallImage': instance.smallImage?.toString(),
'largeImage': instance.largeImage?.toString(),
'runtimeType': instance.$type,
};
SourceAlbum _$SourceAlbumFromJson(Map<String, dynamic> json) => SourceAlbum(
id: json['id'] as String,
artistId: json['artistId'] as String?,
name: json['name'] as String,
albumArtist: json['albumArtist'] as String?,
created: DateTime.parse(json['created'] as String),
coverArt: json['coverArt'] as String?,
year: (json['year'] as num?)?.toInt(),
starred: json['starred'] == null
? null
: DateTime.parse(json['starred'] as String),
genre: json['genre'] as String?,
frequentRank: (json['frequentRank'] as num?)?.toInt(),
recentRank: (json['recentRank'] as num?)?.toInt(),
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$SourceAlbumToJson(SourceAlbum instance) =>
<String, dynamic>{
'id': instance.id,
'artistId': instance.artistId,
'name': instance.name,
'albumArtist': instance.albumArtist,
'created': instance.created.toIso8601String(),
'coverArt': instance.coverArt,
'year': instance.year,
'starred': instance.starred?.toIso8601String(),
'genre': instance.genre,
'frequentRank': instance.frequentRank,
'recentRank': instance.recentRank,
'runtimeType': instance.$type,
};
SourcePlaylist _$SourcePlaylistFromJson(Map<String, dynamic> json) =>
SourcePlaylist(
id: json['id'] as String,
name: json['name'] as String,
comment: json['comment'] as String?,
created: DateTime.parse(json['created'] as String),
changed: DateTime.parse(json['changed'] as String),
coverArt: json['coverArt'] as String?,
owner: json['owner'] as String?,
public: json['public'] as bool?,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$SourcePlaylistToJson(SourcePlaylist instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'comment': instance.comment,
'created': instance.created.toIso8601String(),
'changed': instance.changed.toIso8601String(),
'coverArt': instance.coverArt,
'owner': instance.owner,
'public': instance.public,
'runtimeType': instance.$type,
};
SourceSong _$SourceSongFromJson(Map<String, dynamic> json) => SourceSong(
id: json['id'] as String,
albumId: json['albumId'] as String?,
artistId: json['artistId'] as String?,
title: json['title'] as String,
artist: json['artist'] as String?,
album: json['album'] as String?,
duration: json['duration'] == null
? null
: Duration(microseconds: (json['duration'] as num).toInt()),
track: (json['track'] as num?)?.toInt(),
disc: (json['disc'] as num?)?.toInt(),
starred: json['starred'] == null
? null
: DateTime.parse(json['starred'] as String),
genre: json['genre'] as String?,
coverArt: json['coverArt'] as String?,
$type: json['runtimeType'] as String?,
);
Map<String, dynamic> _$SourceSongToJson(SourceSong instance) =>
<String, dynamic>{
'id': instance.id,
'albumId': instance.albumId,
'artistId': instance.artistId,
'title': instance.title,
'artist': instance.artist,
'album': instance.album,
'duration': instance.duration?.inMicroseconds,
'track': instance.track,
'disc': instance.disc,
'starred': instance.starred?.toIso8601String(),
'genre': instance.genre,
'coverArt': instance.coverArt,
'runtimeType': instance.$type,
};
_SourcePlaylistSong _$SourcePlaylistSongFromJson(Map<String, dynamic> json) =>
_SourcePlaylistSong(
playlistId: json['playlistId'] as String,
songId: json['songId'] as String,
position: (json['position'] as num).toInt(),
);
Map<String, dynamic> _$SourcePlaylistSongToJson(_SourcePlaylistSong instance) =>
<String, dynamic>{
'playlistId': instance.playlistId,
'songId': instance.songId,
'position': instance.position,
};

View File

@@ -3,11 +3,11 @@ import 'models.dart';
abstract class MusicSource {
Future<void> ping();
Stream<SourceAlbum> allAlbums();
Stream<SourceArtist> allArtists();
Stream<SourcePlaylist> allPlaylists();
Stream<SourceSong> allSongs();
Stream<SourcePlaylistSong> allPlaylistSongs();
Stream<Album> allAlbums();
Stream<Artist> allArtists();
Stream<Playlist> allPlaylists();
Stream<Song> allSongs();
Stream<PlaylistSong> allPlaylistSongs();
Uri streamUri(String songId);
Uri downloadUri(String songId);

View File

@@ -2,7 +2,7 @@ import 'package:xml/xml.dart';
import '../models.dart';
SourceArtist mapArtist(XmlElement e, XmlElement? info) => SourceArtist(
Artist mapArtist(XmlElement e, XmlElement? info) => Artist(
id: e.getAttribute('id')!,
name: e.getAttribute('name')!,
starred: DateTime.tryParse(e.getAttribute('starred').toString()),
@@ -10,11 +10,11 @@ SourceArtist mapArtist(XmlElement e, XmlElement? info) => SourceArtist(
largeImage: Uri.tryParse(info?.getElement('largeImageUrl')?.innerText ?? ''),
);
SourceAlbum mapAlbum(
Album mapAlbum(
XmlElement e, {
int? frequentRank,
int? recentRank,
}) => SourceAlbum(
}) => Album(
id: e.getAttribute('id')!,
artistId: e.getAttribute('artistId'),
name: e.getAttribute('name')!,
@@ -28,7 +28,7 @@ SourceAlbum mapAlbum(
recentRank: recentRank,
);
SourcePlaylist mapPlaylist(XmlElement e) => SourcePlaylist(
Playlist mapPlaylist(XmlElement e) => Playlist(
id: e.getAttribute('id')!,
name: e.getAttribute('name')!,
comment: e.getAttribute('comment'),
@@ -36,10 +36,9 @@ SourcePlaylist mapPlaylist(XmlElement e) => SourcePlaylist(
created: DateTime.parse(e.getAttribute('created')!),
changed: DateTime.parse(e.getAttribute('changed')!),
owner: e.getAttribute('owner'),
public: bool.tryParse(e.getAttribute('public').toString()),
);
SourceSong mapSong(XmlElement e) => SourceSong(
Song mapSong(XmlElement e) => Song(
id: e.getAttribute('id')!,
albumId: e.getAttribute('albumId'),
artistId: e.getAttribute('artistId'),
@@ -57,10 +56,10 @@ SourceSong mapSong(XmlElement e) => SourceSong(
genre: e.getAttribute('genre'),
);
SourcePlaylistSong mapPlaylistSong(
PlaylistSong mapPlaylistSong(
int index,
XmlElement e,
) => SourcePlaylistSong(
) => PlaylistSong(
playlistId: e.parentElement!.getAttribute('id')!,
songId: e.getAttribute('id')!,
position: index,

View File

@@ -41,7 +41,7 @@ class SubsonicSource implements MusicSource {
}
@override
Stream<SourceArtist> allArtists() async* {
Stream<Artist> allArtists() async* {
final getArtistsRes = await _pool.withResource(
() => client.get('getArtists'),
);
@@ -58,7 +58,7 @@ class SubsonicSource implements MusicSource {
}
@override
Stream<SourceAlbum> allAlbums() async* {
Stream<Album> allAlbums() async* {
final extras = await Future.wait([
_albumList(
'frequent',
@@ -89,7 +89,7 @@ class SubsonicSource implements MusicSource {
}
@override
Stream<SourcePlaylist> allPlaylists() async* {
Stream<Playlist> allPlaylists() async* {
final res = await _pool.withResource(() => client.get('getPlaylists'));
yield* Stream.fromIterable(
@@ -98,7 +98,7 @@ class SubsonicSource implements MusicSource {
}
@override
Stream<SourcePlaylistSong> allPlaylistSongs() async* {
Stream<PlaylistSong> allPlaylistSongs() async* {
final allPlaylists = await _pool.withResource(
() => client.get('getPlaylists'),
);
@@ -116,7 +116,7 @@ class SubsonicSource implements MusicSource {
}
@override
Stream<SourceSong> allSongs() async* {
Stream<Song> allSongs() async* {
if (await supportsFastSongSync) {
await for (var songs in _songSearch()) {
yield* Stream.fromIterable(songs.map(mapSong));