mirror of
https://github.com/austinried/subtracks.git
synced 2026-02-10 15:02:42 +01:00
songs list and serializable list query
This commit is contained in:
@@ -2,37 +2,78 @@ import 'package:drift/drift.dart';
|
||||
|
||||
import '../../sources/models.dart' as models;
|
||||
import '../database.dart';
|
||||
import '../query.dart';
|
||||
|
||||
part 'library_dao.g.dart';
|
||||
|
||||
typedef AristListItem = ({models.Artist artist, int? albumCount});
|
||||
|
||||
extension on SortDirection {
|
||||
OrderingMode toMode() => switch (this) {
|
||||
SortDirection.asc => OrderingMode.asc,
|
||||
SortDirection.desc => OrderingMode.desc,
|
||||
};
|
||||
}
|
||||
|
||||
@DriftAccessor(include: {'../tables.drift'})
|
||||
class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
||||
with _$LibraryDaoMixin {
|
||||
LibraryDao(super.db);
|
||||
|
||||
Future<List<models.Album>> listAlbums({
|
||||
required int sourceId,
|
||||
required int limit,
|
||||
required int offset,
|
||||
}) {
|
||||
Future<List<models.Album>> listAlbums(AlbumsQuery q) {
|
||||
final query = albums.select()
|
||||
..where(
|
||||
(f) => f.sourceId.equals(sourceId),
|
||||
)
|
||||
..limit(limit, offset: offset);
|
||||
..where((albums) {
|
||||
var filter = albums.sourceId.equals(q.sourceId);
|
||||
for (final queryFilter in q.filter) {
|
||||
filter &= switch (queryFilter) {
|
||||
AlbumsFilterArtistId(:final artistId) => albums.artistId.equals(
|
||||
artistId,
|
||||
),
|
||||
AlbumsFilterYearEquals(:final year) => albums.year.equals(year),
|
||||
_ => CustomExpression(''),
|
||||
};
|
||||
}
|
||||
|
||||
return filter;
|
||||
})
|
||||
..orderBy(
|
||||
q.sort
|
||||
.map(
|
||||
(sort) =>
|
||||
(albums) => OrderingTerm(
|
||||
expression: switch (sort.by) {
|
||||
AlbumsColumn.name => albums.name,
|
||||
AlbumsColumn.created => albums.created,
|
||||
AlbumsColumn.year => albums.year,
|
||||
AlbumsColumn.starred => albums.starred,
|
||||
},
|
||||
mode: sort.dir.toMode(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
_limitQuery(query: query, limit: q.limit, offset: q.offset);
|
||||
|
||||
return query.get();
|
||||
}
|
||||
|
||||
Future<List<AristListItem>> listArtists({
|
||||
required int sourceId,
|
||||
required int limit,
|
||||
required int offset,
|
||||
}) async {
|
||||
Future<List<AristListItem>> listArtists(ArtistsQuery q) {
|
||||
final albumCount = albums.id.count();
|
||||
|
||||
var filter =
|
||||
artists.sourceId.equals(q.sourceId) &
|
||||
albums.sourceId.equals(q.sourceId);
|
||||
|
||||
for (final queryFilter in q.filter) {
|
||||
filter &= switch (queryFilter) {
|
||||
ArtistsFilterStarred(:final starred) =>
|
||||
starred ? artists.starred.isNotNull() : artists.starred.isNull(),
|
||||
ArtistsFilterNameSearch() => CustomExpression(''),
|
||||
_ => CustomExpression(''),
|
||||
};
|
||||
}
|
||||
|
||||
final query =
|
||||
artists.select().join([
|
||||
leftOuterJoin(
|
||||
@@ -41,22 +82,78 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
||||
),
|
||||
])
|
||||
..addColumns([albumCount])
|
||||
..where(
|
||||
artists.sourceId.equals(sourceId) &
|
||||
albums.sourceId.equals(sourceId),
|
||||
)
|
||||
..where(filter)
|
||||
..groupBy([artists.sourceId, artists.id])
|
||||
..orderBy([OrderingTerm.asc(artists.name)])
|
||||
..limit(limit, offset: offset);
|
||||
..orderBy(
|
||||
q.sort
|
||||
.map(
|
||||
(sort) => OrderingTerm(
|
||||
expression: switch (sort.by) {
|
||||
ArtistsColumn.name => artists.name,
|
||||
ArtistsColumn.starred => artists.starred,
|
||||
ArtistsColumn.albumCount => albumCount,
|
||||
},
|
||||
mode: sort.dir.toMode(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
return (await query.get())
|
||||
_limitQuery(query: query, limit: q.limit, offset: q.offset);
|
||||
|
||||
return query
|
||||
.map(
|
||||
(row) => (
|
||||
artist: row.readTable(artists),
|
||||
albumCount: row.read(albumCount) ?? 0,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
.get();
|
||||
}
|
||||
|
||||
Future<List<models.Song>> listSongs(SongsQuery q) {
|
||||
var joinPlaylistSongs = false;
|
||||
var filter = songs.sourceId.equals(q.sourceId);
|
||||
|
||||
for (final queryFilter in q.filter) {
|
||||
switch (queryFilter) {
|
||||
case SongsFilterAlbumId(:final albumId):
|
||||
filter &= songs.albumId.equals(albumId);
|
||||
case SongsFilterPlaylistId(:final playlistId):
|
||||
joinPlaylistSongs = true;
|
||||
filter &= playlistSongs.playlistId.equals(playlistId);
|
||||
}
|
||||
}
|
||||
|
||||
final query =
|
||||
songs.select().join([
|
||||
if (joinPlaylistSongs)
|
||||
leftOuterJoin(
|
||||
playlistSongs,
|
||||
playlistSongs.sourceId.equals(q.sourceId) &
|
||||
playlistSongs.songId.equalsExp(songs.id),
|
||||
),
|
||||
])
|
||||
..where(filter)
|
||||
..orderBy(
|
||||
q.sort
|
||||
.map(
|
||||
(sort) => OrderingTerm(
|
||||
expression: switch (sort.by) {
|
||||
SongsColumn.title => songs.title,
|
||||
SongsColumn.starred => songs.starred,
|
||||
SongsColumn.disc => songs.disc,
|
||||
SongsColumn.track => songs.track,
|
||||
},
|
||||
mode: sort.dir.toMode(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
_limitQuery(query: query, limit: q.limit, offset: q.offset);
|
||||
|
||||
return query.map((row) => (row.readTable(songs))).get();
|
||||
}
|
||||
|
||||
Selectable<models.Album> getAlbum(int sourceId, String id) {
|
||||
@@ -64,4 +161,14 @@ class LibraryDao extends DatabaseAccessor<SubtracksDatabase>
|
||||
(f) => f.sourceId.equals(sourceId) & f.id.equals(id),
|
||||
);
|
||||
}
|
||||
|
||||
void _limitQuery({
|
||||
required LimitContainerMixin query,
|
||||
required int? limit,
|
||||
required int? offset,
|
||||
}) {
|
||||
if (limit != null) {
|
||||
query.limit(limit, offset: offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
121
lib/database/query.dart
Normal file
121
lib/database/query.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'query.freezed.dart';
|
||||
part 'query.g.dart';
|
||||
|
||||
enum SortDirection {
|
||||
asc,
|
||||
desc,
|
||||
}
|
||||
|
||||
enum AlbumsColumn {
|
||||
name,
|
||||
created,
|
||||
year,
|
||||
starred,
|
||||
}
|
||||
|
||||
enum ArtistsColumn {
|
||||
name,
|
||||
starred,
|
||||
albumCount,
|
||||
}
|
||||
|
||||
enum SongsColumn {
|
||||
title,
|
||||
starred,
|
||||
disc,
|
||||
track,
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class SortingTerm with _$SortingTerm {
|
||||
const factory SortingTerm.albums({
|
||||
required SortDirection dir,
|
||||
required AlbumsColumn by,
|
||||
}) = AlbumsSortingTerm;
|
||||
|
||||
const factory SortingTerm.artists({
|
||||
required SortDirection dir,
|
||||
required ArtistsColumn by,
|
||||
}) = ArtistsSortingTerm;
|
||||
|
||||
const factory SortingTerm.songs({
|
||||
required SortDirection dir,
|
||||
required SongsColumn by,
|
||||
}) = SongsSortingTerm;
|
||||
|
||||
factory SortingTerm.fromJson(Map<String, Object?> json) =>
|
||||
_$SortingTermFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class AlbumsQuery with _$AlbumsQuery {
|
||||
const factory AlbumsQuery({
|
||||
required int sourceId,
|
||||
@Default(IListConst([])) IList<AlbumsFilter> filter,
|
||||
required IList<AlbumsSortingTerm> sort,
|
||||
int? limit,
|
||||
int? offset,
|
||||
}) = _AlbumsQuery;
|
||||
|
||||
factory AlbumsQuery.fromJson(Map<String, Object?> json) =>
|
||||
_$AlbumsQueryFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class AlbumsFilter with _$AlbumsFilter {
|
||||
const factory AlbumsFilter.artistId(String artistId) = AlbumsFilterArtistId;
|
||||
const factory AlbumsFilter.yearEquals(int year) = AlbumsFilterYearEquals;
|
||||
|
||||
factory AlbumsFilter.fromJson(Map<String, Object?> json) =>
|
||||
_$AlbumsFilterFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ArtistsQuery with _$ArtistsQuery {
|
||||
const factory ArtistsQuery({
|
||||
required int sourceId,
|
||||
@Default(IListConst([])) IList<ArtistsFilter> filter,
|
||||
required IList<ArtistsSortingTerm> sort,
|
||||
int? limit,
|
||||
int? offset,
|
||||
}) = _ArtistsQuery;
|
||||
|
||||
factory ArtistsQuery.fromJson(Map<String, Object?> json) =>
|
||||
_$ArtistsQueryFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class ArtistsFilter with _$ArtistsFilter {
|
||||
const factory ArtistsFilter.nameSearch(String name) = ArtistsFilterNameSearch;
|
||||
const factory ArtistsFilter.starred(bool starred) = ArtistsFilterStarred;
|
||||
|
||||
factory ArtistsFilter.fromJson(Map<String, Object?> json) =>
|
||||
_$ArtistsFilterFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class SongsQuery with _$SongsQuery {
|
||||
const factory SongsQuery({
|
||||
required int sourceId,
|
||||
@Default(IListConst([])) IList<SongsFilter> filter,
|
||||
required IList<SongsSortingTerm> sort,
|
||||
int? limit,
|
||||
int? offset,
|
||||
}) = _SongsQuery;
|
||||
|
||||
factory SongsQuery.fromJson(Map<String, Object?> json) =>
|
||||
_$SongsQueryFromJson(json);
|
||||
}
|
||||
|
||||
@freezed
|
||||
abstract class SongsFilter with _$SongsFilter {
|
||||
const factory SongsFilter.albumId(String albumId) = SongsFilterAlbumId;
|
||||
const factory SongsFilter.playlistId(String playlistId) =
|
||||
SongsFilterPlaylistId;
|
||||
|
||||
factory SongsFilter.fromJson(Map<String, Object?> json) =>
|
||||
_$SongsFilterFromJson(json);
|
||||
}
|
||||
2311
lib/database/query.freezed.dart
Normal file
2311
lib/database/query.freezed.dart
Normal file
File diff suppressed because it is too large
Load Diff
226
lib/database/query.g.dart
Normal file
226
lib/database/query.g.dart
Normal file
@@ -0,0 +1,226 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'query.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
AlbumsSortingTerm _$AlbumsSortingTermFromJson(Map<String, dynamic> json) =>
|
||||
AlbumsSortingTerm(
|
||||
dir: $enumDecode(_$SortDirectionEnumMap, json['dir']),
|
||||
by: $enumDecode(_$AlbumsColumnEnumMap, json['by']),
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AlbumsSortingTermToJson(AlbumsSortingTerm instance) =>
|
||||
<String, dynamic>{
|
||||
'dir': _$SortDirectionEnumMap[instance.dir]!,
|
||||
'by': _$AlbumsColumnEnumMap[instance.by]!,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
const _$SortDirectionEnumMap = {
|
||||
SortDirection.asc: 'asc',
|
||||
SortDirection.desc: 'desc',
|
||||
};
|
||||
|
||||
const _$AlbumsColumnEnumMap = {
|
||||
AlbumsColumn.name: 'name',
|
||||
AlbumsColumn.created: 'created',
|
||||
AlbumsColumn.year: 'year',
|
||||
AlbumsColumn.starred: 'starred',
|
||||
};
|
||||
|
||||
ArtistsSortingTerm _$ArtistsSortingTermFromJson(Map<String, dynamic> json) =>
|
||||
ArtistsSortingTerm(
|
||||
dir: $enumDecode(_$SortDirectionEnumMap, json['dir']),
|
||||
by: $enumDecode(_$ArtistsColumnEnumMap, json['by']),
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ArtistsSortingTermToJson(ArtistsSortingTerm instance) =>
|
||||
<String, dynamic>{
|
||||
'dir': _$SortDirectionEnumMap[instance.dir]!,
|
||||
'by': _$ArtistsColumnEnumMap[instance.by]!,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
const _$ArtistsColumnEnumMap = {
|
||||
ArtistsColumn.name: 'name',
|
||||
ArtistsColumn.starred: 'starred',
|
||||
ArtistsColumn.albumCount: 'albumCount',
|
||||
};
|
||||
|
||||
SongsSortingTerm _$SongsSortingTermFromJson(Map<String, dynamic> json) =>
|
||||
SongsSortingTerm(
|
||||
dir: $enumDecode(_$SortDirectionEnumMap, json['dir']),
|
||||
by: $enumDecode(_$SongsColumnEnumMap, json['by']),
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SongsSortingTermToJson(SongsSortingTerm instance) =>
|
||||
<String, dynamic>{
|
||||
'dir': _$SortDirectionEnumMap[instance.dir]!,
|
||||
'by': _$SongsColumnEnumMap[instance.by]!,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
const _$SongsColumnEnumMap = {
|
||||
SongsColumn.title: 'title',
|
||||
SongsColumn.starred: 'starred',
|
||||
SongsColumn.disc: 'disc',
|
||||
SongsColumn.track: 'track',
|
||||
};
|
||||
|
||||
_AlbumsQuery _$AlbumsQueryFromJson(Map<String, dynamic> json) => _AlbumsQuery(
|
||||
sourceId: (json['sourceId'] as num).toInt(),
|
||||
filter: json['filter'] == null
|
||||
? const IListConst([])
|
||||
: IList<AlbumsFilter>.fromJson(
|
||||
json['filter'],
|
||||
(value) => AlbumsFilter.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
sort: IList<AlbumsSortingTerm>.fromJson(
|
||||
json['sort'],
|
||||
(value) => AlbumsSortingTerm.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
limit: (json['limit'] as num?)?.toInt(),
|
||||
offset: (json['offset'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AlbumsQueryToJson(_AlbumsQuery instance) =>
|
||||
<String, dynamic>{
|
||||
'sourceId': instance.sourceId,
|
||||
'filter': instance.filter.toJson((value) => value),
|
||||
'sort': instance.sort.toJson((value) => value),
|
||||
'limit': instance.limit,
|
||||
'offset': instance.offset,
|
||||
};
|
||||
|
||||
AlbumsFilterArtistId _$AlbumsFilterArtistIdFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => AlbumsFilterArtistId(
|
||||
json['artistId'] as String,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AlbumsFilterArtistIdToJson(
|
||||
AlbumsFilterArtistId instance,
|
||||
) => <String, dynamic>{
|
||||
'artistId': instance.artistId,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
AlbumsFilterYearEquals _$AlbumsFilterYearEqualsFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => AlbumsFilterYearEquals(
|
||||
(json['year'] as num).toInt(),
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$AlbumsFilterYearEqualsToJson(
|
||||
AlbumsFilterYearEquals instance,
|
||||
) => <String, dynamic>{'year': instance.year, 'runtimeType': instance.$type};
|
||||
|
||||
_ArtistsQuery _$ArtistsQueryFromJson(Map<String, dynamic> json) =>
|
||||
_ArtistsQuery(
|
||||
sourceId: (json['sourceId'] as num).toInt(),
|
||||
filter: json['filter'] == null
|
||||
? const IListConst([])
|
||||
: IList<ArtistsFilter>.fromJson(
|
||||
json['filter'],
|
||||
(value) => ArtistsFilter.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
sort: IList<ArtistsSortingTerm>.fromJson(
|
||||
json['sort'],
|
||||
(value) => ArtistsSortingTerm.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
limit: (json['limit'] as num?)?.toInt(),
|
||||
offset: (json['offset'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ArtistsQueryToJson(_ArtistsQuery instance) =>
|
||||
<String, dynamic>{
|
||||
'sourceId': instance.sourceId,
|
||||
'filter': instance.filter.toJson((value) => value),
|
||||
'sort': instance.sort.toJson((value) => value),
|
||||
'limit': instance.limit,
|
||||
'offset': instance.offset,
|
||||
};
|
||||
|
||||
ArtistsFilterNameSearch _$ArtistsFilterNameSearchFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => ArtistsFilterNameSearch(
|
||||
json['name'] as String,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ArtistsFilterNameSearchToJson(
|
||||
ArtistsFilterNameSearch instance,
|
||||
) => <String, dynamic>{'name': instance.name, 'runtimeType': instance.$type};
|
||||
|
||||
ArtistsFilterStarred _$ArtistsFilterStarredFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => ArtistsFilterStarred(
|
||||
json['starred'] as bool,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ArtistsFilterStarredToJson(
|
||||
ArtistsFilterStarred instance,
|
||||
) => <String, dynamic>{
|
||||
'starred': instance.starred,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
_SongsQuery _$SongsQueryFromJson(Map<String, dynamic> json) => _SongsQuery(
|
||||
sourceId: (json['sourceId'] as num).toInt(),
|
||||
filter: json['filter'] == null
|
||||
? const IListConst([])
|
||||
: IList<SongsFilter>.fromJson(
|
||||
json['filter'],
|
||||
(value) => SongsFilter.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
sort: IList<SongsSortingTerm>.fromJson(
|
||||
json['sort'],
|
||||
(value) => SongsSortingTerm.fromJson(value as Map<String, dynamic>),
|
||||
),
|
||||
limit: (json['limit'] as num?)?.toInt(),
|
||||
offset: (json['offset'] as num?)?.toInt(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SongsQueryToJson(_SongsQuery instance) =>
|
||||
<String, dynamic>{
|
||||
'sourceId': instance.sourceId,
|
||||
'filter': instance.filter.toJson((value) => value),
|
||||
'sort': instance.sort.toJson((value) => value),
|
||||
'limit': instance.limit,
|
||||
'offset': instance.offset,
|
||||
};
|
||||
|
||||
SongsFilterAlbumId _$SongsFilterAlbumIdFromJson(Map<String, dynamic> json) =>
|
||||
SongsFilterAlbumId(
|
||||
json['albumId'] as String,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SongsFilterAlbumIdToJson(SongsFilterAlbumId instance) =>
|
||||
<String, dynamic>{
|
||||
'albumId': instance.albumId,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
|
||||
SongsFilterPlaylistId _$SongsFilterPlaylistIdFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => SongsFilterPlaylistId(
|
||||
json['playlistId'] as String,
|
||||
$type: json['runtimeType'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SongsFilterPlaylistIdToJson(
|
||||
SongsFilterPlaylistId instance,
|
||||
) => <String, dynamic>{
|
||||
'playlistId': instance.playlistId,
|
||||
'runtimeType': instance.$type,
|
||||
};
|
||||
Reference in New Issue
Block a user